[fusion-commits] r1499 - in trunk: . lib/OpenLayers lib/OpenLayers/Lang lib/OpenLayers/theme/default

svn_fusion at osgeo.org svn_fusion at osgeo.org
Fri Sep 5 10:39:22 EDT 2008


Author: madair
Date: 2008-09-05 10:39:19 -0400 (Fri, 05 Sep 2008)
New Revision: 1499

Modified:
   trunk/fusion.cfg
   trunk/lib/OpenLayers/Lang/en.js
   trunk/lib/OpenLayers/Lang/fr.js
   trunk/lib/OpenLayers/OpenLayers.js
   trunk/lib/OpenLayers/OpenLayersCompressed.js
   trunk/lib/OpenLayers/OpenLayersUncompressed.js
   trunk/lib/OpenLayers/theme/default/style.css
Log:
re #119: interim commit to bring OL to trunk version prior to v2.7 release

Modified: trunk/fusion.cfg
===================================================================
--- trunk/fusion.cfg	2008-09-05 14:31:06 UTC (rev 1498)
+++ trunk/fusion.cfg	2008-09-05 14:39:19 UTC (rev 1499)
@@ -4,11 +4,10 @@
 [first]
 OpenLayers/SingleFile.js
 OpenLayers.js
+OpenLayers/Util.js
+OpenLayers/Console.js
 OpenLayers/BaseTypes.js
 OpenLayers/BaseTypes/Class.js
-OpenLayers/Util.js
-OpenLayers/Ajax.js
-OpenLayers/Console.js
 OpenLayers/BaseTypes/Size.js
 OpenLayers/BaseTypes/Bounds.js
 OpenLayers/BaseTypes/Element.js
@@ -18,6 +17,8 @@
 [last]
 
 [include]
+OpenLayers/Request.js
+OpenLayers/Ajax.js
 OpenLayers/Events.js
 OpenLayers/Map.js
 OpenLayers/Layer.js
@@ -26,22 +27,24 @@
 OpenLayers/Layer/WMS.js
 OpenLayers/Layer/MapGuide.js
 OpenLayers/Layer/MapServer.js
+OpenLayers/Layer/Vector.js
 OpenLayers/Tile.js
 OpenLayers/Tile/Image.js
 OpenLayers/Control/OverviewMap.js
 OpenLayers/Control/Navigation.js
 OpenLayers/Control/PanZoom.js
 OpenLayers/Control/ArgParser.js
-OpenLayers/Control/DragPan.js
-OpenLayers/Control/MousePosition.js
-OpenLayers/Control/ScaleLine.js
-OpenLayers/Handler/Box.js
+OpenLayers/Control/DrawFeature.js
 OpenLayers/Handler/Click.js
-OpenLayers/Handler/MouseWheel.js
+OpenLayers/Handler/Point.js
+OpenLayers/Handler/Path.js
+OpenLayers/Handler/Polygon.js
 OpenLayers/Projection.js
 OpenLayers/Marker.js
 OpenLayers/Icon.js
 OpenLayers/Marker/Box.js
+OpenLayers/Tween.js
+OpenLayers/Lang.js
 
 [exclude]
 Firebug/firebug.js

Modified: trunk/lib/OpenLayers/Lang/en.js
===================================================================
--- trunk/lib/OpenLayers/Lang/en.js	2008-09-05 14:31:06 UTC (rev 1498)
+++ trunk/lib/OpenLayers/Lang/en.js	2008-09-05 14:39:19 UTC (rev 1499)
@@ -75,7 +75,7 @@
         "To get rid of this message, select a new BaseLayer " +
         "in the layer switcher in the upper-right corner.<br><br>" +
         "Most likely, this is because the ${layerLib} library " +
-        "script was either not correctly included.<br><br>" +
+        "script was not correctly included.<br><br>" +
         "Developers: For help getting this working correctly, " +
         "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
         "target='_blank'>click here</a>",

Modified: trunk/lib/OpenLayers/Lang/fr.js
===================================================================
--- trunk/lib/OpenLayers/Lang/fr.js	2008-09-05 14:31:06 UTC (rev 1498)
+++ trunk/lib/OpenLayers/Lang/fr.js	2008-09-05 14:39:19 UTC (rev 1499)
@@ -14,6 +14,108 @@
  */
 OpenLayers.Lang.fr = {
 
-    'overlays': "Couches de superposition"
+    'unhandledRequest': "Requête non gérée, retournant ${statusText}",
 
+    'permalink': "Permalien",
+
+    'overlays': "Calques",
+
+    'baseLayer': "Calque de base",
+
+    'sameProjection':
+        "La carte de situation ne fonctionne que lorsque sa projection est la même que celle de la carte principale",
+
+    'readNotImplemented': "Lecture non implémentée.",
+
+    'writeNotImplemented': "Ecriture non implémentée.",
+
+    'noFID': "Impossible de mettre à jour un objet sans identifiant (fid).",
+
+    'errorLoadingGML': "Erreur au chargement du fichier GML ${url}",
+
+    'browserNotSupported':
+        "Votre navigateur ne supporte pas le rendu vectoriel. Les renderers actuellement supportés sont : \n${renderers}",
+
+    'componentShouldBe': "addFeatures : le composant devrait être de type ${geomType}",
+
+    // console message
+    'getFeatureError':
+        "getFeatureFromEvent a été appelé sur un calque sans renderer. Cela signifie généralement que vous " +
+        "avez détruit cette couche, mais que vous avez conservé un handler qui lui était associé.",
+
+    // console message
+    'minZoomLevelError':
+        "La propriété minZoomLevel doit seulement être utilisée " +
+        "pour des couches FixedZoomLevels-descendent. Le fait que " +
+        "cette couche WFS vérifie la présence de minZoomLevel " +
+        "est une relique du passé. Nous ne pouvons toutefois la " +
+        "supprimer sans casser des applications qui pourraient en dépendre." +
+        " C'est pourquoi nous la déprécions -- la vérification du minZoomLevel " +
+        "sera supprimée en version 3.0. A la place, merci d'utiliser " +
+        "les paramètres de résolutions min/max tel que décrit sur : " +
+        "http://trac.openlayers.org/wiki/SettingZoomLevels",
+
+    'commitSuccess': "Transaction WFS : SUCCES ${response}",
+
+    'commitFailed': "Transaction WFS : ECHEC ${response}",
+
+    'googleWarning':
+        "La couche Google n'a pas été en mesure de se charger correctement.<br><br>" +
+        "Pour supprimer ce message, choisissez une nouvelle BaseLayer " +
+        "dans le sélecteur de couche en haut à droite.<br><br>" +
+        "Cela est possiblement causé par la non-inclusion de la " +
+        "librairie Google Maps, ou alors parce que la clé de l'API " +
+        "ne correspond pas à votre site.<br><br>" +
+        "Développeurs : pour savoir comment corriger ceci, " +
+        "<a href='http://trac.openlayers.org/wiki/Google' " +
+        "target='_blank'>cliquez ici</a>",
+
+    'getLayerWarning':
+        "La couche ${layerType} n'est pas en mesure de se charger correctement.<br><br>" +
+        "Pour supprimer ce message, choisissez une nouvelle BaseLayer " +
+        "dans le sélecteur de couche en haut à droite.<br><br>" +
+        "Cela est possiblement causé par la non-inclusion de la " +
+        "librairie ${layerLib}.<br><br>" +
+        "Développeurs : pour savoir comment corriger ceci, " +
+        "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
+        "target='_blank'>cliquez ici</a>",
+
+    'scale': "Echelle ~ 1 : ${scaleDenom}",
+
+    // console message
+    'layerAlreadyAdded':
+        "Vous avez essayé d'ajouter à la carte le calque : ${layerName}, mais il est déjà présent",
+
+    // console message
+    'reprojectDeprecated':
+        "Vous utilisez l'option 'reproject' " +
+        "sur la couche ${layerName}. Cette option est dépréciée : " +
+        "Son usage permettait d'afficher des données au dessus de couches raster commerciales." + 
+        "Cette fonctionalité est maintenant supportée en utilisant le support de la projection " +
+        "Mercator Sphérique. Plus d'information est disponible sur " +
+        "http://trac.openlayers.org/wiki/SphericalMercator.",
+
+    // console message
+    'methodDeprecated':
+        "Cette méthode est dépréciée, et sera supprimée à la version 3.0. " +
+        "Merci d'utiliser ${newMethod} à la place.",
+
+    // console message
+    'boundsAddError': "Vous devez passer les deux valeurs x et y à la fonction add.",
+
+    // console message
+    'lonlatAddError': "Vous devez passer les deux valeurs lon et lat à la fonction add.",
+
+    // console message
+    'pixelAddError': "Vous devez passer les deux valeurs x et y à la fonction add.",
+
+    // console message
+    'unsupportedGeometryType': "Type de géométrie non supporté : ${geomType}",
+
+    // console message
+    'pagePositionFailed':
+        "OpenLayers.Util.pagePosition a échoué: l'élément d'id ${elemId} pourrait être mal positionné.",
+    
+    'end': ''
+
 };

Modified: trunk/lib/OpenLayers/OpenLayers.js
===================================================================
--- trunk/lib/OpenLayers/OpenLayers.js	2008-09-05 14:31:06 UTC (rev 1498)
+++ trunk/lib/OpenLayers/OpenLayers.js	2008-09-05 14:39:19 UTC (rev 1499)
@@ -43,6 +43,15 @@
 *
 **/
 
+/**
+ * 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/SingleFile.js
    ====================================================================== */
@@ -102,7 +111,7 @@
             var scriptName = OpenLayers._scriptName;
          
             var scripts = document.getElementsByTagName('script');
-            for (var i = 0; i < scripts.length; i++) {
+            for (var i=0, len=scripts.length; i<len; i++) {
                 var src = scripts[i].getAttribute('src');
                 if (src) {
                     var index = src.lastIndexOf(scriptName); 
@@ -146,6 +155,8 @@
             "Rico/Corner.js",
             "Rico/Color.js",
             "OpenLayers/Ajax.js",
+            "OpenLayers/Request.js",
+            "OpenLayers/Request/XMLHttpRequest.js",
             "OpenLayers/Events.js",
             "OpenLayers/Projection.js",
             "OpenLayers/Map.js",
@@ -238,8 +249,12 @@
             "OpenLayers/Renderer.js",
             "OpenLayers/Renderer/Elements.js",
             "OpenLayers/Renderer/SVG.js",
+            "OpenLayers/Renderer/Canvas.js",
             "OpenLayers/Renderer/VML.js",
             "OpenLayers/Layer/Vector.js",
+            "OpenLayers/Strategy.js",
+            "OpenLayers/Strategy/Fixed.js",
+            "OpenLayers/Protocol.js",
             "OpenLayers/Layer/PointTrack.js",
             "OpenLayers/Layer/GML.js",
             "OpenLayers/Style.js",
@@ -257,9 +272,14 @@
             "OpenLayers/Format/WFS.js",
             "OpenLayers/Format/WKT.js",
             "OpenLayers/Format/OSM.js",
+            "OpenLayers/Format/GPX.js",
             "OpenLayers/Format/SLD.js",
             "OpenLayers/Format/SLD/v1.js",
             "OpenLayers/Format/SLD/v1_0_0.js",
+            "OpenLayers/Format/SLD/v1.js",
+            "OpenLayers/Format/Filter.js",
+            "OpenLayers/Format/Filter/v1.js",
+            "OpenLayers/Format/Filter/v1_0_0.js",
             "OpenLayers/Format/Text.js",
             "OpenLayers/Format/JSON.js",
             "OpenLayers/Format/GeoJSON.js",
@@ -281,7 +301,7 @@
             var allScriptTags = new Array(jsfiles.length);
         }
         var host = OpenLayers._getScriptLocation() + "lib/";    
-        for (var i = 0; i < jsfiles.length; i++) {
+        for (var i=0, len=jsfiles.length; i<len; i++) {
             if (docWrite) {
                 allScriptTags[i] = "<script src='" + host + jsfiles[i] +
                                    "'></script>"; 
@@ -394,7 +414,7 @@
     camelize: function(str) {
         var oStringList = str.split('-');
         var camelizedString = oStringList[0];
-        for (var i = 1; i < oStringList.length; i++) {
+        for (var i=1, len=oStringList.length; i<len; i++) {
             var s = oStringList[i];
             camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
         }
@@ -428,7 +448,7 @@
         }
         var tokens = template.split("${");
         var item, last, replacement;
-        for(var i=1; i<tokens.length; i++) {
+        for(var i=1, len=tokens.length; i<len; i++) {
             item = tokens[i];
             last = item.indexOf("}"); 
             if(last > 0) {
@@ -444,6 +464,31 @@
             }
         }
         return tokens.join("");
+    },
+    
+    /**
+     * Property: OpenLayers.String.numberRegEx
+     * Used to test strings as numbers.
+     */
+    numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
+    
+    /**
+     * APIFunction: OpenLayers.String.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);
     }
 
 };
@@ -812,7 +857,7 @@
     };
     var extended = {};
     var parent;
-    for(var i=0; i<arguments.length; ++i) {
+    for(var i=0, len=arguments.length; i<len; ++i) {
         if(typeof arguments[i] == "function") {
             // get the prototype of the superclass
             parent = arguments[i].prototype;
@@ -863,7 +908,7 @@
 OpenLayers.Class.inherit = function () {
     var superClass = arguments[0];
     var proto = new superClass(OpenLayers.Class.isPrototype);
-    for (var i = 1; i < arguments.length; i++) {
+    for (var i=1, len=arguments.length; i<len; i++) {
         if (typeof arguments[i] == "function") {
             var mixin = arguments[i];
             arguments[i] = new mixin(OpenLayers.Class.isPrototype);
@@ -893,7 +938,7 @@
 OpenLayers.Util.getElement = function() {
     var elements = [];
 
-    for (var i = 0; i < arguments.length; i++) {
+    for (var i=0, len=arguments.length; i<len; i++) {
         var element = arguments[i];
         if (typeof element == 'string') {
             element = document.getElementById(element);
@@ -927,7 +972,8 @@
  * {Object} The destination object.
  */
 OpenLayers.Util.extend = function(destination, source) {
-    if(destination && source) {
+    destination = destination || {};
+    if(source) {
         for(var property in source) {
             var value = source[property];
             if(value !== undefined) {
@@ -1012,7 +1058,7 @@
  */
 OpenLayers.Util.indexOf = function(array, obj) {
 
-    for(var i=0; i < array.length; i++) {
+    for(var i=0, len=array.length; i<len; i++) {
         if (array[i] == obj) {
             return i;
         }
@@ -1075,10 +1121,9 @@
  * 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.d
+ * set a particular attribute.
+ * Note - zIndex is NOT set on the resulting div.
  * 
- * Note: zIndex is NOT set
- * 
  * Parameters:
  * id - {String} An identifier for this element.  If no id is
  *               passed an identifier will be created 
@@ -1241,8 +1286,27 @@
  */
 OpenLayers.Util.onImageLoadError = function() {
     this._attempts = (this._attempts) ? (this._attempts + 1) : 1;
-    if(this._attempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
-        this.src = this.src;
+    if (this._attempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
+        var urls = this.urls;
+        if (urls && urls instanceof Array && urls.length > 1){
+            var src = this.src.toString();
+            var current_url, k;
+            for (k = 0; current_url = urls[k]; k++){
+                if(src.indexOf(current_url) != -1){
+                    break;
+                }
+            }
+            var guess = Math.floor(urls.length * Math.random())
+            var new_url = urls[guess];
+            k = 0;
+            while(new_url == current_url && k++ < 4){
+                guess = Math.floor(urls.length * Math.random())
+                new_url = urls[guess];
+            }
+            this.src = src.replace(current_url, new_url);
+        } else {
+            this.src = this.src;
+        }
     } else {
         this.style.backgroundColor = OpenLayers.Util.onImageLoadErrorColor;
     }
@@ -1294,8 +1358,8 @@
                                                position, border, sizing, 
                                                opacity) {
 
-    OpenLayers.Util.modifyDOMElement(div, id, px, sz, 
-                                     null, null, null, opacity);
+    OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
+                                     null, null, opacity);
 
     var img = div.childNodes[0];
 
@@ -1306,8 +1370,9 @@
                                      "relative", border);
     
     if (OpenLayers.Util.alphaHack()) {
-
-        div.style.display = "inline-block";
+        if(div.style.display != "none") {
+            div.style.display = "inline-block";
+        }
         if (sizing == null) {
             sizing = "scale";
         }
@@ -1400,7 +1465,7 @@
  *     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
@@ -1453,7 +1518,7 @@
         if (typeof value == 'object' && value.constructor == Array) {
           /* value is an array; encode items and separate with "," */
           var encodedItemArray = [];
-          for (var itemIndex=0; itemIndex<value.length; itemIndex++) {
+          for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
             encodedItemArray.push(encodeURIComponent(value[itemIndex]));
           }
           encodedValue = encodedItemArray.join(",");
@@ -1504,7 +1569,7 @@
 OpenLayers.Util.Try = function() {
     var returnValue = null;
 
-    for (var i = 0; i < arguments.length; i++) {
+    for (var i=0, len=arguments.length; i<len; i++) {
       var lambda = arguments[i];
       try {
         returnValue = lambda();
@@ -1553,7 +1618,7 @@
  */
 OpenLayers.Util._getNodes=function(nodes, tagName) {
     var retArray = [];
-    for (var i=0;i<nodes.length;i++) {
+    for (var i=0, len=nodes.length; i<len; i++) {
         if (nodes[i].nodeName==tagName) {
             retArray.push(nodes[i]);
         }
@@ -1729,7 +1794,7 @@
         
     var parameters = {};
     var pairs = paramsString.split(/[&;]/);
-    for(var i = 0; i < pairs.length; ++i) {
+    for(var i=0, len=pairs.length; i<len; ++i) {
         var keyValue = pairs[i].split('=');
         if (keyValue[0]) {
             var key = decodeURIComponent(keyValue[0]);
@@ -1737,7 +1802,7 @@
 
             //decode individual values
             value = value.split(",");
-            for(var j=0; j < value.length; j++) {
+            for(var j=0, jlen=value.length; j<jlen; j++) {
                 value[j] = decodeURIComponent(value[j]);
             }
 
@@ -1924,12 +1989,7 @@
     while(element) {
 
         if(element == document.body) {
-            // FIXME: IE, when passed 'window' as the forElement, treats it as
-            // equal to document.body, but window.style fails, so getStyle
-            // fails, so we are paranoid and check this here. This check should
-            // probably move into element.getStyle in 2.6.
-            if(child && child.style && 
-               OpenLayers.Element.getStyle(child, 'position') == 'absolute') {
+            if(OpenLayers.Element.getStyle(child, 'position') == 'absolute') {
                 break;
             }
         }
@@ -1999,7 +2059,7 @@
     //compare all keys (host, port, etc)
     for(var key in urlObj1) {
         if (options.test) {
-            alert(key + "\n1:" + urlObj1[key] + "\n2:" + urlObj2[key]);
+            OpenLayers.Console.userError(key + "\n1:" + urlObj1[key] + "\n2:" + urlObj2[key]);
         }
         var val1 = urlObj1[key];
         var val2 = urlObj2[key];
@@ -2232,17 +2292,21 @@
  *     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}
+ *     displayClass - {String} Optional parameter.  A CSS class name(s) string
+ *         to provide the CSS context of the rendered content.
  * 
  * Returns:
  * {OpenLayers.Size}
  */
-OpenLayers.Util.getRenderedDimensions = function(contentHTML, size) {
+OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
     
-    var w = h = null;
+    var w, h;
     
     // create temp container div with restricted size
     var container = document.createElement("div");
@@ -2253,11 +2317,18 @@
     //fix a dimension, if specified.
     if (size) {
         if (size.w) {
-            w = container.style.width = size.w;
+            w = size.w;
+            container.style.width = w + "px";
         } else if (size.h) {
-            h = container.style.height = 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");
@@ -2352,1003 +2423,340 @@
     return scrollbarWidth;
 };
 /* ======================================================================
-    OpenLayers/Ajax.js
+    Rico/Corner.js
    ====================================================================== */
 
-/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
- * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
- * full text of the license. */
-
-
-OpenLayers.ProxyHost = "";
-//OpenLayers.ProxyHost = "examples/proxy.cgi?url=";
-
-/**
- * Ajax reader for OpenLayers
- *
- *  @uri url to do remote XML http get
- *  @param {String} 'get' format params (x=y&a=b...)
- *  @who object to handle callbacks for this request
- *  @complete  the function to be called on success 
- *  @failure  the function to be called on failure
+/*
+ * This file has been edited substantially from the Rico-released
+ * version by the OpenLayers development team.
  *  
- *   example usage from a caller:
+ *  Copyright 2005 Sabre Airline Solutions  
  *  
- *     caps: function(request) {
- *      -blah-  
- *     },
+ *  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
  *  
- *     OpenLayers.loadURL(url,params,this,caps);
+ *         http://www.apache.org/licenses/LICENSE-2.0  
+ *  
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the * License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or
+ * implied. See the License for the specific language governing
+ * permissions * and limitations under the License.
  *
- * Notice the above example does not provide an error handler; a default empty
- * handler is provided which merely logs the error if a failure handler is not 
- * supplied
- *
- */
+ */  
+OpenLayers.Rico = new Object();
+OpenLayers.Rico.Corner = {
 
+    round: function(e, options) {
+        e = OpenLayers.Util.getElement(e);
+        this._setOptions(options);
 
-/** 
-* @param {} request
-*/
-OpenLayers.nullHandler = function(request) {
-    alert(OpenLayers.i18n("unhandledRequest", {'statusText':request.statusText}));
-};
-
-/** 
- * Function: loadURL
- * Background load a document.
- *
- * Parameters:
- * uri - {String} URI of source doc
- * params - {String} Params on get (doesnt seem to work)
- * caller - {Object} object which gets callbacks
- * onComplete - {Function} Optional callback for success.  The callback
- *     will be called with this set to caller and will receive the request
- *     object as an argument.
- * onFailure - {Function} Optional callback for failure.  In the event of
- *     a failure, the callback will be called with this set to caller and will
- *     receive the request object as an argument.
- *
- * Returns:
- * {XMLHttpRequest}  The request object.  To abort loading, call
- *     request.abort().
- */
-OpenLayers.loadURL = function(uri, params, caller,
-                                  onComplete, onFailure) {
-
-    var success = (onComplete) ? OpenLayers.Function.bind(onComplete, caller)
-                                : OpenLayers.nullHandler;
-
-    var failure = (onFailure) ? OpenLayers.Function.bind(onFailure, caller)
-                           : OpenLayers.nullHandler;
-
-    // from prototype.js
-    var request = new OpenLayers.Ajax.Request(
-        uri, 
-        {
-            method: 'get', 
-            parameters: params,
-            onComplete: success, 
-            onFailure: failure
+        var color = this.options.color;
+        if ( this.options.color == "fromElement" ) {
+            color = this._background(e);
         }
-    );
-    return request.transport;
-};
-
-/** 
- * Function: parseXMLString
- * Parse XML into a doc structure
- * 
- * Parameters:
- * text - {String} 
- * 
- * Returns:
- * {?} Parsed AJAX Responsev
- */
-OpenLayers.parseXMLString = function(text) {
-
-    //MS sucks, if the server is bad it dies
-    var index = text.indexOf('<');
-    if (index > 0) {
-        text = text.substring(index);
-    }
-
-    var ajaxResponse = OpenLayers.Util.Try(
-        function() {
-            var xmldom = new ActiveXObject('Microsoft.XMLDOM');
-            xmldom.loadXML(text);
-            return xmldom;
-        },
-        function() {
-            return new DOMParser().parseFromString(text, 'text/xml');
-        },
-        function() {
-            var req = new XMLHttpRequest();
-            req.open("GET", "data:" + "text/xml" +
-                     ";charset=utf-8," + encodeURIComponent(text), false);
-            if (req.overrideMimeType) {
-                req.overrideMimeType("text/xml");
-            }
-            req.send(null);
-            return req.responseXML;
+        var bgColor = this.options.bgColor;
+        if ( this.options.bgColor == "fromParent" ) {
+            bgColor = this._background(e.offsetParent);
         }
-    );
-
-    return ajaxResponse;
-};
-
-
-/**
- * Namespace: OpenLayers.Ajax
- */
-OpenLayers.Ajax = {
-
-    /**
-     * Method: emptyFunction
-     */
-    emptyFunction: function () {},
-
-    /**
-     * Method: getTransport
-     * 
-     * Returns: 
-     * {Object} Transport mechanism for whichever browser we're in, or false if
-     *          none available.
-     */
-    getTransport: function() {
-        return OpenLayers.Util.Try(
-            function() {return new XMLHttpRequest();},
-            function() {return new ActiveXObject('Msxml2.XMLHTTP');},
-            function() {return new ActiveXObject('Microsoft.XMLHTTP');}
-        ) || false;
+        this._roundCornersImpl(e, color, bgColor);
     },
 
-    /**
-     * Property: activeRequestCount
-     * {Integer}
-     */
-    activeRequestCount: 0
-};
+    /**   This is a helper function to change the background
+    *     color of <div> that has had Rico rounded corners added.
+    *
+    *     It seems we cannot just set the background color for the
+    *     outer <div> so each <span> element used to create the
+    *     corners must have its background color set individually.
+    *
+    * @param {DOM} theDiv - A child of the outer <div> that was
+    *                        supplied to the `round` method.
+    *
+    * @param {String} newColor - The new background color to use.
+    */
+    changeColor: function(theDiv, newColor) {
+   
+        theDiv.style.backgroundColor = newColor;
 
-/**
- * Namespace: OpenLayers.Ajax.Responders
- * {Object}
- */
-OpenLayers.Ajax.Responders = {
-  
-    /**
-     * Property: responders
-     * {Array}
-     */
-    responders: [],
-
-    /**
-     * Method: register
-     *  
-     * Parameters:
-     * responderToAdd - {?}
-     */
-    register: function(responderToAdd) {
-        for (var i = 0; i < this.responders.length; i++){
-            if (responderToAdd == this.responders[i]){
-                return;
-            }
-        }
-        this.responders.push(responderToAdd);
-    },
-
-    /**
-     * Method: unregister
-     *  
-     * Parameters:
-     * responderToRemove - {?}
-     */
-    unregister: function(responderToRemove) {
-        OpenLayers.Util.removeItem(this.reponders, responderToRemove);
-    },
-
-    /**
-     * Method: dispatch
-     * 
-     * Parameters:
-     * callback - {?}
-     * request - {?}
-     * transport - {?}
-     */
-    dispatch: function(callback, request, transport) {
-        var responder;
-        for (var i = 0; i < this.responders.length; i++) {
-            responder = this.responders[i];
-     
-            if (responder[callback] && 
-                typeof responder[callback] == 'function') {
-                try {
-                    responder[callback].apply(responder, 
-                                              [request, transport]);
-                } catch (e) {}
-            }
-        }
-    }
-};
-
-OpenLayers.Ajax.Responders.register({
-    /** 
-     * Function: onCreate
-     */
-    onCreate: function() {
-        OpenLayers.Ajax.activeRequestCount++;
-    },
-
-    /**
-     * Function: onComplete
-     */
-     onComplete: function() {
-         OpenLayers.Ajax.activeRequestCount--;
-     }
-});
-
-/**
- * Class: OpenLayers.Ajax.Base
- */
-OpenLayers.Ajax.Base = OpenLayers.Class({
-      
-    /**
-     * Constructor: OpenLayers.Ajax.Base
-     * 
-     * Parameters: 
-     * options - {Object}
-     */
-    initialize: function(options) {
-        this.options = {
-            method:       'post',
-            asynchronous: true,
-            contentType:  'application/xml',
-            parameters:   ''
-        };
-        OpenLayers.Util.extend(this.options, options || {});
+        var spanElements = theDiv.parentNode.getElementsByTagName("span");
         
-        this.options.method = this.options.method.toLowerCase();
-        
-        if (typeof this.options.parameters == 'string') {
-            this.options.parameters = 
-                OpenLayers.Util.getParameters(this.options.parameters);
+        for (var currIdx = 0; currIdx < spanElements.length; currIdx++) {
+            spanElements[currIdx].style.backgroundColor = newColor;
         }
-    }
-});
+    }, 
 
-/**
- * Class: OpenLayers.Ajax.Request
- *
- * Inherit:
- *  - <OpenLayers.Ajax.Base>
- */
-OpenLayers.Ajax.Request = OpenLayers.Class(OpenLayers.Ajax.Base, {
 
-    /**
-     * Property: _complete
-     *
-     * {Boolean}
-     */
-    _complete: false,
-      
-    /**
-     * Constructor: OpenLayers.Ajax.Request
-     * 
-     * Parameters: 
-     * url - {String}
-     * options - {Object}
-     */
-    initialize: function(url, options) {
-        OpenLayers.Ajax.Base.prototype.initialize.apply(this, [options]);
+    /**   This is a helper function to change the background
+    *     opacity of <div> that has had Rico rounded corners added.
+    *
+    *     See changeColor (above) for algorithm explanation
+    *
+    * @param {DOM} theDiv A child of the outer <div> that was
+    *                        supplied to the `round` method.
+    *
+    * @param {int} newOpacity The new opacity to use (0-1).
+    */
+    changeOpacity: function(theDiv, newOpacity) {
+   
+        var mozillaOpacity = newOpacity;
+        var ieOpacity = 'alpha(opacity=' + newOpacity * 100 + ')';
         
-        if (OpenLayers.ProxyHost && OpenLayers.String.startsWith(url, "http")) {
-            url = OpenLayers.ProxyHost + encodeURIComponent(url);
-        }
-        
-        this.transport = OpenLayers.Ajax.getTransport();
-        this.request(url);
-    },
+        theDiv.style.opacity = mozillaOpacity;
+        theDiv.style.filter = ieOpacity;
 
-    /**
-     * Method: request
-     * 
-     * Parameters:
-     * url - {String}
-     */
-    request: function(url) {
-        this.url = url;
-        this.method = this.options.method;
-        var params = OpenLayers.Util.extend({}, this.options.parameters);
+        var spanElements = theDiv.parentNode.getElementsByTagName("span");
         
-        if (this.method != 'get' && this.method != 'post') {
-            // simulate other verbs over post
-            params['_method'] = this.method;
-            this.method = 'post';
+        for (var currIdx = 0; currIdx < spanElements.length; currIdx++) {
+            spanElements[currIdx].style.opacity = mozillaOpacity;
+            spanElements[currIdx].style.filter = ieOpacity;
         }
 
-        this.parameters = params;        
-        
-        if (params = OpenLayers.Util.getParameterString(params)) {
-            // when GET, append parameters to URL
-            if (this.method == 'get') {
-                this.url += ((this.url.indexOf('?') > -1) ? '&' : '?') + params;
-            } else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
-                params += '&_=';
-            }
-        }
-        try {
-            var response = new OpenLayers.Ajax.Response(this);
-            if (this.options.onCreate) {
-                this.options.onCreate(response);
-            }
-            
-            OpenLayers.Ajax.Responders.dispatch('onCreate', 
-                                                this, 
-                                                response);
-    
-            this.transport.open(this.method.toUpperCase(), 
-                                this.url,
-                                this.options.asynchronous);
-    
-            if (this.options.asynchronous) {
-                window.setTimeout(
-                    OpenLayers.Function.bind(this.respondToReadyState, this, 1),
-                    10);
-            }
-            
-            this.transport.onreadystatechange = 
-                OpenLayers.Function.bind(this.onStateChange, this);    
-            this.setRequestHeaders();
-    
-            this.body =  this.method == 'post' ?
-                (this.options.postBody || params) : null;
-            this.transport.send(this.body);
-    
-            // Force Firefox to handle ready state 4 for synchronous requests
-            if (!this.options.asynchronous && 
-                this.transport.overrideMimeType) {
-                this.onStateChange();
-            }
-        } catch (e) {
-            this.dispatchException(e);
-        }
     },
 
-    /**
-     * Method: onStateChange
-     */
-    onStateChange: function() {
-        var readyState = this.transport.readyState;
-        if (readyState > 1 && !((readyState == 4) && this._complete)) {
-            this.respondToReadyState(this.transport.readyState);
-        }
-    },
-     
-    /**
-     * Method: setRequestHeaders
-     */
-    setRequestHeaders: function() {
-        var headers = {
-            'X-Requested-With': 'XMLHttpRequest',
-            'Accept': 'text/javascript, text/html, application/xml, text/xml, */*',
-            'OpenLayers': true
-        };
+    /** this function takes care of redoing the rico cornering
+    *    
+    *    you can't just call updateRicoCorners() again and pass it a 
+    *    new options string. you have to first remove the divs that 
+    *    rico puts on top and below the content div.
+    *
+    * @param {DOM} theDiv - A child of the outer <div> that was
+    *                        supplied to the `round` method.
+    *
+    * @param {Object} options - list of options
+    */
+    reRound: function(theDiv, options) {
 
-        if (this.method == 'post') {
-            headers['Content-type'] = this.options.contentType +
-                (this.options.encoding ? '; charset=' + this.options.encoding : '');
-    
-            /* Force "Connection: close" for older Mozilla browsers to work
-             * around a bug where XMLHttpRequest sends an incorrect
-             * Content-length header. See Mozilla Bugzilla #246651.
-             */
-            if (this.transport.overrideMimeType &&
-                (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) {
-                headers['Connection'] = 'close';
-            }
-        }
-        // user-defined headers
-        if (typeof this.options.requestHeaders == 'object') {    
-            var extras = this.options.requestHeaders;
-            
-            if (typeof extras.push == 'function') {
-                for (var i = 0, length = extras.length; i < length; i += 2) {
-                    headers[extras[i]] = extras[i+1];
-                }
-            } else {
-                for (var i in extras) {
-                    headers[i] = pair[i];
-                }
-            }
-        }
-        
-        for (var name in headers) {
-            this.transport.setRequestHeader(name, headers[name]);
-        }
-    },
-    
-    /**
-     * Method: success
-     *
-     * Returns:
-     * {Boolean} - 
-     */
-    success: function() {
-        var status = this.getStatus();
-        return !status || (status >=200 && status < 300);
-    },
-    
-    /**
-     * Method: getStatus
-     *
-     * Returns:
-     * {Integer} - Status
-     */
-    getStatus: function() {
-        try {
-            return this.transport.status || 0;
-        } catch (e) {
-            return 0;
-        }
-    },
+        var topRico = theDiv.parentNode.childNodes[0];
+        //theDiv would be theDiv.parentNode.childNodes[1]
+        var bottomRico = theDiv.parentNode.childNodes[2];
+       
+        theDiv.parentNode.removeChild(topRico);
+        theDiv.parentNode.removeChild(bottomRico); 
 
-    /**
-     * Method: respondToReadyState
-     *
-     * Parameters:
-     * readyState - {?}
-     */
-    respondToReadyState: function(readyState) {
-        var state = OpenLayers.Ajax.Request.Events[readyState];
-        var response = new OpenLayers.Ajax.Response(this);
-    
-        if (state == 'Complete') {
-            try {
-                this._complete = true;
-                (this.options['on' + response.status] ||
-                    this.options['on' + (this.success() ? 'Success' : 'Failure')] ||
-                    OpenLayers.Ajax.emptyFunction)(response);
-            } catch (e) {
-                this.dispatchException(e);
-            }
-    
-            var contentType = response.getHeader('Content-type');
-        }
-    
-        try {
-            (this.options['on' + state] || 
-             OpenLayers.Ajax.emptyFunction)(response);
-             OpenLayers.Ajax.Responders.dispatch('on' + state, 
-                                                 this, 
-                                                 response);
-        } catch (e) {
-            this.dispatchException(e);
-        }
-    
-        if (state == 'Complete') {
-            // avoid memory leak in MSIE: clean up
-            this.transport.onreadystatechange = OpenLayers.Ajax.emptyFunction;
-        }
-    },
-    
-    /**
-     * Method: getHeader
-     * 
-     * Parameters:
-     * name - {String} Header name
-     *
-     * Returns:
-     * {?} - response header for the given name
-     */
-    getHeader: function(name) {
-        try {
-            return this.transport.getResponseHeader(name);
-        } catch (e) {
-            return null;
-        }
-    },
+        this.round(theDiv.parentNode, options);
+    }, 
 
-    /**
-     * Method: dispatchException
-     * If the optional onException function is set, execute it
-     * and then dispatch the call to any other listener registered
-     * for onException.
-     * 
-     * If no optional onException function is set, we suspect that
-     * the user may have also not used
-     * OpenLayers.Ajax.Responders.register to register a listener
-     * for the onException call.  To make sure that something
-     * gets done with this exception, only dispatch the call if there
-     * are listeners.
-     *
-     * If you explicitly want to swallow exceptions, set
-     * request.options.onException to an empty function (function(){})
-     * or register an empty function with <OpenLayers.Ajax.Responders>
-     * for onException.
-     * 
-     * Parameters:
-     * exception - {?}
-     */
-    dispatchException: function(exception) {
-        var handler = this.options.onException;
-        if(handler) {
-            // call options.onException and alert any other listeners
-            handler(this, exception);
-            OpenLayers.Ajax.Responders.dispatch('onException', this, exception);
-        } else {
-            // check if there are any other listeners
-            var listener = false;
-            var responders = OpenLayers.Ajax.Responders.responders;
-            for (var i = 0; i < responders.length; i++) {
-                if(responders[i].onException) {
-                    listener = true;
-                    break;
-                }
-            }
-            if(listener) {
-                // call all listeners
-                OpenLayers.Ajax.Responders.dispatch('onException', this, exception);
-            } else {
-                // let the exception through
-                throw exception;
-            }
-        }
-    }
-});
+   _roundCornersImpl: function(e, color, bgColor) {
+      if(this.options.border) {
+         this._renderBorder(e,bgColor);
+      }
+      if(this._isTopRounded()) {
+         this._roundTopCorners(e,color,bgColor);
+      }
+      if(this._isBottomRounded()) {
+         this._roundBottomCorners(e,color,bgColor);
+      }
+   },
 
-/** 
- * Property: Events
- * {Array(String)}
- */
-OpenLayers.Ajax.Request.Events =
-  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+   _renderBorder: function(el,bgColor) {
+      var borderValue = "1px solid " + this._borderColor(bgColor);
+      var borderL = "border-left: "  + borderValue;
+      var borderR = "border-right: " + borderValue;
+      var style   = "style='" + borderL + ";" + borderR +  "'";
+      el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>";
+   },
 
-/**
- * Class: OpenLayers.Ajax.Response
- */
-OpenLayers.Ajax.Response = OpenLayers.Class({
+   _roundTopCorners: function(el, color, bgColor) {
+      var corner = this._createCorner(bgColor);
+      for(var i=0 ; i < this.options.numSlices ; i++ ) {
+         corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
+      }
+      el.style.paddingTop = 0;
+      el.insertBefore(corner,el.firstChild);
+   },
 
-    /**
-     * Property: status
-     *
-     * {Integer}
-     */
-    status: 0,
-    
+   _roundBottomCorners: function(el, color, bgColor) {
+      var corner = this._createCorner(bgColor);
+      for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- ) {
+         corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
+      }
+      el.style.paddingBottom = 0;
+      el.appendChild(corner);
+   },
 
-    /**
-     * Property: statusText
-     *
-     * {String}
-     */
-    statusText: '',
-      
-    /**
-     * Constructor: OpenLayers.Ajax.Response
-     * 
-     * Parameters: 
-     * request - {Object}
-     */
-    initialize: function(request) {
-        this.request = request;
-        var transport = this.transport = request.transport,
-            readyState = this.readyState = transport.readyState;
-        
-        if ((readyState > 2 &&
-            !(!!(window.attachEvent && !window.opera))) ||
-            readyState == 4) {
-            this.status       = this.getStatus();
-            this.statusText   = this.getStatusText();
-            this.responseText = transport.responseText == null ?
-                '' : String(transport.responseText);
-        }
-        
-        if(readyState == 4) {
-            var xml = transport.responseXML;
-            this.responseXML  = xml === undefined ? null : xml;
-        }
-    },
-    
-    /**
-     * Method: getStatus
-     */
-    getStatus: OpenLayers.Ajax.Request.prototype.getStatus,
-    
-    /**
-     * Method: getStatustext
-     *
-     * Returns:
-     * {String} - statusText
-     */
-    getStatusText: function() {
-        try {
-            return this.transport.statusText || '';
-        } catch (e) {
-            return '';
-        }
-    },
-    
-    /**
-     * Method: getHeader
-     */
-    getHeader: OpenLayers.Ajax.Request.prototype.getHeader,
-    
-    /** 
-     * Method: getResponseHeader
-     *
-     * Returns:
-     * {?} - response header for given name
-     */
-    getResponseHeader: function(name) {
-        return this.transport.getResponseHeader(name);
-    }
-});
+   _createCorner: function(bgColor) {
+      var corner = document.createElement("div");
+      corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
+      return corner;
+   },
 
+   _createCornerSlice: function(color,bgColor, n, position) {
+      var slice = document.createElement("span");
 
-/**
- * Function: getElementsByTagNameNS
- * 
- * Parameters:
- * parentnode - {?}
- * nsuri - {?}
- * nsprefix - {?}
- * tagname - {?}
- * 
- * Returns:
- * {?}
- */
-OpenLayers.Ajax.getElementsByTagNameNS  = function(parentnode, nsuri, 
-                                                   nsprefix, tagname) {
-    var elem = null;
-    if (parentnode.getElementsByTagNameNS) {
-        elem = parentnode.getElementsByTagNameNS(nsuri, tagname);
-    } else {
-        elem = parentnode.getElementsByTagName(nsprefix + ':' + tagname);
-    }
-    return elem;
-};
+      var inStyle = slice.style;
+      inStyle.backgroundColor = color;
+      inStyle.display  = "block";
+      inStyle.height   = "1px";
+      inStyle.overflow = "hidden";
+      inStyle.fontSize = "1px";
 
+      var borderColor = this._borderColor(color,bgColor);
+      if ( this.options.border && n == 0 ) {
+         inStyle.borderTopStyle    = "solid";
+         inStyle.borderTopWidth    = "1px";
+         inStyle.borderLeftWidth   = "0px";
+         inStyle.borderRightWidth  = "0px";
+         inStyle.borderBottomWidth = "0px";
+         inStyle.height            = "0px"; // assumes css compliant box model
+         inStyle.borderColor       = borderColor;
+      }
+      else if(borderColor) {
+         inStyle.borderColor = borderColor;
+         inStyle.borderStyle = "solid";
+         inStyle.borderWidth = "0px 1px";
+      }
 
-/**
- * Function: serializeXMLToString
- * Wrapper function around XMLSerializer, which doesn't exist/work in
- *     IE/Safari. We need to come up with a way to serialize in those browser:
- *     for now, these browsers will just fail. #535, #536
- *
- * Parameters: 
- * xmldom {XMLNode} xml dom to serialize
- * 
- * Returns:
- * {?}
- */
-OpenLayers.Ajax.serializeXMLToString = function(xmldom) {
-    var serializer = new XMLSerializer();
-    var data = serializer.serializeToString(xmldom);
-    return data;
-};
-/* ======================================================================
-    OpenLayers/Console.js
-   ====================================================================== */
+      if ( !this.options.compact && (n == (this.options.numSlices-1)) ) {
+         inStyle.height = "2px";
+      }
+      this._setMargin(slice, n, position);
+      this._setBorder(slice, n, position);
+      return slice;
+   },
 
-/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
- * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
- * full text of the license. */
+   _setOptions: function(options) {
+      this.options = {
+         corners : "all",
+         color   : "fromElement",
+         bgColor : "fromParent",
+         blend   : true,
+         border  : false,
+         compact : false
+      };
+      OpenLayers.Util.extend(this.options, options || {});
 
-/**
- * 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() {},
+      this.options.numSlices = this.options.compact ? 2 : 4;
+      if ( this._isTransparent() ) {
+         this.options.blend = false;
+      }
+   },
 
-    /**
-     * 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() {},
+   _whichSideTop: function() {
+      if ( this._hasString(this.options.corners, "all", "top") ) {
+         return "";
+      }
+      if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 ) {
+         return "";
+      }
+      if (this.options.corners.indexOf("tl") >= 0) {
+         return "left";
+      } else if (this.options.corners.indexOf("tr") >= 0) {
+          return "right";
+      }
+      return "";
+   },
 
-    /**
-     * 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() {},
+   _whichSideBottom: function() {
+      if ( this._hasString(this.options.corners, "all", "bottom") ) {
+         return "";
+      }
+      if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 ) {
+         return "";
+      }
 
-    /**
-     * 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() {},
+      if(this.options.corners.indexOf("bl") >=0) {
+         return "left";
+      } else if(this.options.corners.indexOf("br")>=0) {
+         return "right";
+      }
+      return "";
+   },
 
-    /**
-     * 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() {},
+   _borderColor : function(color,bgColor) {
+      if ( color == "transparent" ) {
+         return bgColor;
+      } else if ( this.options.border ) {
+         return this.options.border;
+      } else if ( this.options.blend ) {
+         return this._blend( bgColor, color );
+      } else {
+         return "";
+      }
+   },
 
-    /**
-     * 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() {},
+   _setMargin: function(el, n, corners) {
+      var marginSize = this._marginSize(n);
+      var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
 
-    /**
-     * 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() {},
+      if ( whichSide == "left" ) {
+         el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
+      }
+      else if ( whichSide == "right" ) {
+         el.style.marginRight = marginSize + "px"; el.style.marginLeft  = "0px";
+      }
+      else {
+         el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
+      }
+   },
 
-    /**
-     * 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() {},
+   _setBorder: function(el,n,corners) {
+      var borderSize = this._borderSize(n);
+      var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+      if ( whichSide == "left" ) {
+         el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
+      }
+      else if ( whichSide == "right" ) {
+         el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth  = "0px";
+      }
+      else {
+         el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+      }
+      if (this.options.border != false) {
+        el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+      }
+   },
 
-    /**
-     * 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() {},
+   _marginSize: function(n) {
+      if ( this._isTransparent() ) {
+         return 0;
+      }
+      var marginSizes          = [ 5, 3, 2, 1 ];
+      var blendedMarginSizes   = [ 3, 2, 1, 0 ];
+      var compactMarginSizes   = [ 2, 1 ];
+      var smBlendedMarginSizes = [ 1, 0 ];
 
-    /**
-     * 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() {},
+      if ( this.options.compact && this.options.blend ) {
+         return smBlendedMarginSizes[n];
+      } else if ( this.options.compact ) {
+         return compactMarginSizes[n];
+      } else if ( this.options.blend ) {
+         return blendedMarginSizes[n];
+      } else {
+         return marginSizes[n];
+      }
+   },
 
-    /**
-     * APIFunction: timeEnd
-     * Stops a timer created by a call to OpenLayers.Console.time(name) and
-     * writes the time elapsed.
-     *
-     * Parameters:
-     * name - {String}
-     */
-    timeEnd: function() {},
+   _borderSize: function(n) {
+      var transparentBorderSizes = [ 5, 3, 2, 1 ];
+      var blendedBorderSizes     = [ 2, 1, 1, 1 ];
+      var compactBorderSizes     = [ 1, 0 ];
+      var actualBorderSizes      = [ 0, 2, 0, 0 ];
 
-    /**
-     * 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() {},
+      if ( this.options.compact && (this.options.blend || this._isTransparent()) ) {
+         return 1;
+      } else if ( this.options.compact ) {
+         return compactBorderSizes[n];
+      } else if ( this.options.blend ) {
+         return blendedBorderSizes[n];
+      } else if ( this.options.border ) {
+         return actualBorderSizes[n];
+      } else if ( this._isTransparent() ) {
+         return transparentBorderSizes[n];
+      }
+      return 0;
+   },
 
-    /**
-     * 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"
+   _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) { return true; } return false; },
+   _blend: function(c1, c2) { var cc1 = OpenLayers.Rico.Color.createFromHex(c1); cc1.blend(OpenLayers.Rico.Color.createFromHex(c2)); return cc1; },
+   _background: function(el) { try { return OpenLayers.Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
+   _isTransparent: function() { return this.options.color == "transparent"; },
+   _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
+   _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
+   _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
 };
-
-/**
- * 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.
-     */
-    if(window.console) {
-        var scripts = document.getElementsByTagName("script");
-        for(var i=0; i<scripts.length; ++i) {
-            if(scripts[i].src.indexOf("firebug.js") != -1) {
-                OpenLayers.Util.extend(OpenLayers.Console, console);
-                break;
-            }
-        }
-    }
-})();
 /* ======================================================================
-    OpenLayers/BaseTypes/Size.js
-   ====================================================================== */
-
-/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
- * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
- * full text of the license. */
-
-/**
- * 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. 
-     * (ex. <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>}
-     *
-     * 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/BaseTypes/Bounds.js
    ====================================================================== */
 
@@ -3574,6 +2982,48 @@
     },
 
     /**
+     * Method: scale
+     * Scales the bounds around a pixel or lonlat. Note that the new 
+     *     bounds may return non-integer properties, even if a pixel
+     *     is passed. 
+     * 
+     * Parameters:
+     * ratio - {Float} 
+     * origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
+     *          Default is center.
+     *
+     * Returns:
+     * {<OpenLayers.Bound>} A new bounds that is scaled by ratio
+     *                      from origin.
+     */
+
+    scale: function(ratio, origin){
+        if(origin == null){
+            origin = this.getCenterLonLat();
+        }
+
+        var bounds = [];
+        
+        var origx,origy;
+
+        // get origin coordinates
+        if(origin.CLASS_NAME == "OpenLayers.LonLat"){
+            origx = origin.lon;
+            origy = origin.lat;
+        } else {
+            origx = origin.x;
+            origy = origin.y;
+        }
+
+        var left = (this.left - origx) * ratio + origx;
+        var bottom = (this.bottom - origy) * ratio + origy;
+        var right = (this.right - origx) * ratio + origx;
+        var top = (this.top - origy) * ratio + origy;
+
+        return new OpenLayers.Bounds(left, bottom, right, top);
+    },
+
+    /**
      * APIMethod: add
      * 
      * Parameters:
@@ -3987,7 +3437,7 @@
      * element - {DOMElement} Actually user can pass any number of elements
      */
     toggle: function() {
-        for (var i = 0; i < arguments.length; i++) {
+        for (var i=0, len=arguments.length; i<len; i++) {
             var element = OpenLayers.Util.getElement(arguments[i]);
             var display = OpenLayers.Element.visible(element) ? 'hide' 
                                                               : 'show';
@@ -4004,7 +3454,7 @@
      * element - {DOMElement} Actually user can pass any number of elements
      */
     hide: function() {
-        for (var i = 0; i < arguments.length; i++) {
+        for (var i=0, len=arguments.length; i<len; i++) {
             var element = OpenLayers.Util.getElement(arguments[i]);
             element.style.display = 'none';
         }
@@ -4018,7 +3468,7 @@
      * element - {DOMElement} Actually user can pass any number of elements
      */
     show: function() {
-        for (var i = 0; i < arguments.length; i++) {
+        for (var i=0, len=arguments.length; i<len; i++) {
             var element = OpenLayers.Util.getElement(arguments[i]);
             element.style.display = '';
         }
@@ -4083,6 +3533,86 @@
     },
 
     /**
+     * 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:
@@ -4094,25 +3624,29 @@
      */
     getStyle: function(element, style) {
         element = OpenLayers.Util.getElement(element);
-        var 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 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';
+            }
         }
     
-        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;
     }
 
@@ -4237,7 +3771,8 @@
 
     /**
      * APIMethod: transform
-     * Transform the LonLat object from source to dest. 
+     * 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. 
@@ -4430,6 +3965,343 @@
     CLASS_NAME: "OpenLayers.Pixel"
 });
 /* ======================================================================
+    OpenLayers/BaseTypes/Size.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * 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. 
+     * (ex. <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>}
+     *
+     * 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-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * 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:
+     * object - {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.
+     */
+    if(window.console) {
+        var scripts = document.getElementsByTagName("script");
+        for(var i=0, len=scripts.length; i<len; ++i) {
+            if(scripts[i].src.indexOf("firebug.js") != -1) {
+                OpenLayers.Util.extend(OpenLayers.Console, console);
+                break;
+            }
+        }
+    }
+})();
+/* ======================================================================
     OpenLayers/Control.js
    ====================================================================== */
 
@@ -4478,7 +4350,7 @@
  * >     },
  * >
  * >     notice: function (bounds) {
- * >         alert(bounds);
+ * >         OpenLayers.Console.userError(bounds);
  * >     }
  * > }); 
  * > map.addControl(control);
@@ -4611,7 +4483,9 @@
         if(this.eventListeners instanceof Object) {
             this.events.on(this.eventListeners);
         }
-        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+        if (this.id == null) {
+            this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+        }
     },
 
     /**
@@ -5100,6 +4974,1841 @@
  */
 OpenLayers.i18n = OpenLayers.Lang.translate;
 /* ======================================================================
+    OpenLayers/Popup.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * Class: OpenLayers.Popup
+ * A popup is a small div that can opened and closed on the map.
+ * Typically opened in response to clicking on a marker.  
+ * See <OpenLayers.Marker>.  Popup's don't require their own
+ * layer and are added the the map using the <OpenLayers.Map.addPopup>
+ * method.
+ *
+ * Example:
+ * (code)
+ * popup = new OpenLayers.Popup("chicken", 
+ *                    new OpenLayers.LonLat(5,40),
+ *                    new OpenLayers.Size(200,200),
+ *                    "example popup",
+ *                    true);
+ *       
+ * map.addPopup(popup);
+ * (end)
+ */
+OpenLayers.Popup = OpenLayers.Class({
+
+    /** 
+     * Property: events  
+     * {<OpenLayers.Events>} custom event manager 
+     */
+    events: null,
+    
+    /** Property: id
+     * {String} the unique identifier assigned to this popup.
+     */
+    id: "",
+
+    /** 
+     * Property: lonlat 
+     * {<OpenLayers.LonLat>} the position of this popup on the map
+     */
+    lonlat: null,
+
+    /** 
+     * Property: div 
+     * {DOMElement} the div that contains this popup.
+     */
+    div: null,
+
+    /** 
+     * Property: contentSize 
+     * {<OpenLayers.Size>} the width and height of the content.
+     */
+    contentSize: null,    
+
+    /** 
+     * Property: size 
+     * {<OpenLayers.Size>} the width and height of the popup.
+     */
+    size: null,    
+
+    /** 
+     * Property: contentHTML 
+     * {String} An HTML string for this popup to display.
+     */
+    contentHTML: null,
+    
+    /** 
+     * Property: backgroundColor 
+     * {String} the background color used by the popup.
+     */
+    backgroundColor: "",
+    
+    /** 
+     * Property: opacity 
+     * {float} the opacity of this popup (between 0.0 and 1.0)
+     */
+    opacity: "",
+
+    /** 
+     * Property: border 
+     * {String} the border size of the popup.  (eg 2px)
+     */
+    border: "",
+    
+    /** 
+     * Property: contentDiv 
+     * {DOMElement} a reference to the element that holds the content of
+     *              the div.
+     */
+    contentDiv: null,
+    
+    /** 
+     * Property: groupDiv 
+     * {DOMElement} First and only child of 'div'. The group Div contains the
+     *     'contentDiv' and the 'closeDiv'.
+     */
+    groupDiv: null,
+
+    /** 
+     * Property: closeDiv
+     * {DOMElement} the optional closer image
+     */
+    closeDiv: null,
+
+    /** 
+     * APIProperty: autoSize
+     * {Boolean} Resize the popup to auto-fit the contents.
+     *     Default is false.
+     */
+    autoSize: false,
+
+    /**
+     * APIProperty: minSize
+     * {<OpenLayers.Size>} Minimum size allowed for the popup's contents.
+     */
+    minSize: null,
+
+    /**
+     * APIProperty: maxSize
+     * {<OpenLayers.Size>} Maximum size allowed for the popup's contents.
+     */
+    maxSize: null,
+
+    /** 
+     * Property: displayClass
+     * {String} The CSS class of the popup.
+     */
+    displayClass: "olPopup",
+
+    /** 
+     * Property: contentDisplayClass
+     * {String} The CSS class of the popup content div.
+     */
+    contentDisplayClass: "olPopupContent",
+
+    /** 
+     * Property: padding 
+     * {int or <OpenLayers.Bounds>} An extra opportunity to specify internal 
+     *     padding of the content div inside the popup. This was originally
+     *     confused with the css padding as specified in style.css's 
+     *     'olPopupContent' class. We would like to get rid of this altogether,
+     *     except that it does come in handy for the framed and anchoredbubble
+     *     popups, who need to maintain yet another barrier between their 
+     *     content and the outer border of the popup itself. 
+     * 
+     *     Note that in order to not break API, we must continue to support 
+     *     this property being set as an integer. Really, though, we'd like to 
+     *     have this specified as a Bounds object so that user can specify
+     *     distinct left, top, right, bottom paddings. With the 3.0 release
+     *     we can make this only a bounds.
+     */
+    padding: 0,
+
+    /** 
+     * Method: fixPadding
+     * To be removed in 3.0, this function merely helps us to deal with the 
+     *     case where the user may have set an integer value for padding, 
+     *     instead of an <OpenLayers.Bounds> object.
+     */
+    fixPadding: function() {
+        if (typeof this.padding == "number") {
+            this.padding = new OpenLayers.Bounds(
+                this.padding, this.padding, this.padding, this.padding
+            );
+        }
+    },
+
+    /**
+     * APIProperty: panMapIfOutOfView
+     * {Boolean} When drawn, pan map such that the entire popup is visible in
+     *     the current viewport (if necessary).
+     *     Default is false.
+     */
+    panMapIfOutOfView: false,
+    
+    /** 
+     * Property: map 
+     * {<OpenLayers.Map>} this gets set in Map.js when the popup is added to the map
+     */
+    map: null,
+
+    /** 
+    * Constructor: OpenLayers.Popup
+    * Create a popup.
+    * 
+    * Parameters: 
+    * id - {String} a unqiue identifier for this popup.  If null is passed
+    *               an identifier will be automatically generated. 
+    * lonlat - {<OpenLayers.LonLat>}  The position on the map the popup will
+    *                                 be shown.
+    * contentSize - {<OpenLayers.Size>} The size of the content.
+    * contentHTML - {String}          An HTML string to display inside the   
+    *                                 popup.
+    * closeBox - {Boolean}            Whether to display a close box inside
+    *                                 the popup.
+    * closeBoxCallback - {Function}   Function to be called on closeBox click.
+    */
+    initialize:function(id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback) {
+        if (id == null) {
+            id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+        }
+
+        this.id = id;
+        this.lonlat = lonlat;
+
+        this.contentSize = (contentSize != null) ? contentSize 
+                                  : new OpenLayers.Size(
+                                                   OpenLayers.Popup.WIDTH,
+                                                   OpenLayers.Popup.HEIGHT);
+        if (contentHTML != null) { 
+             this.contentHTML = contentHTML;
+        }
+        this.backgroundColor = OpenLayers.Popup.COLOR;
+        this.opacity = OpenLayers.Popup.OPACITY;
+        this.border = OpenLayers.Popup.BORDER;
+
+        this.div = OpenLayers.Util.createDiv(this.id, null, null, 
+                                             null, null, null, "hidden");
+        this.div.className = this.displayClass;
+        
+        var groupDivId = this.id + "_GroupDiv";
+        this.groupDiv = OpenLayers.Util.createDiv(groupDivId, null, null, 
+                                                    null, "relative", null,
+                                                    "hidden");
+
+        var id = this.div.id + "_contentDiv";
+        this.contentDiv = OpenLayers.Util.createDiv(id, null, this.contentSize.clone(), 
+                                                    null, "relative");
+        this.contentDiv.className = this.contentDisplayClass;
+        this.groupDiv.appendChild(this.contentDiv);
+        this.div.appendChild(this.groupDiv);
+
+        if (closeBox) {
+            this.addCloseBox(closeBoxCallback);
+        } 
+
+        this.registerEvents();
+    },
+
+    /** 
+     * Method: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+
+        this.id = null;
+        this.lonlat = null;
+        this.size = null;
+        this.contentHTML = null;
+        
+        this.backgroundColor = null;
+        this.opacity = null;
+        this.border = null;
+        
+        this.events.destroy();
+        this.events = null;
+        
+        if (this.closeDiv) {
+            OpenLayers.Event.stopObservingElement(this.closeDiv); 
+            this.groupDiv.removeChild(this.closeDiv);
+        }
+        this.closeDiv = null;
+        
+        this.div.removeChild(this.groupDiv);
+        this.groupDiv = null;
+
+        if (this.map != null) {
+            this.map.removePopup(this);
+        }
+        this.map = null;
+        this.div = null;
+        
+        this.autoSize = null;
+        this.minSize = null;
+        this.maxSize = null;
+        this.padding = null;
+        this.panMapIfOutOfView = null;
+    },
+
+    /** 
+    * Method: draw
+    * Constructs the elements that make up the popup.
+    *
+    * Parameters:
+    * px - {<OpenLayers.Pixel>} the position the popup in pixels.
+    * 
+    * Returns:
+    * {DOMElement} Reference to a div that contains the drawn popup
+    */
+    draw: function(px) {
+        if (px == null) {
+            if ((this.lonlat != null) && (this.map != null)) {
+                px = this.map.getLayerPxFromLonLat(this.lonlat);
+            }
+        }
+        
+        //listen to movestart, moveend to disable overflow (FF bug)
+        if (OpenLayers.Util.getBrowserName() == 'firefox') {
+            this.map.events.register("movestart", this, function() {
+                var style = document.defaultView.getComputedStyle(
+                    this.contentDiv, null
+                );
+                var currentOverflow = style.getPropertyValue("overflow");
+                if (currentOverflow != "hidden") {
+                    this.contentDiv._oldOverflow = currentOverflow;
+                    this.contentDiv.style.overflow = "hidden";
+                }
+            });
+            this.map.events.register("moveend", this, function() {
+                var oldOverflow = this.contentDiv._oldOverflow;
+                if (oldOverflow) {
+                    this.contentDiv.style.overflow = oldOverflow;
+                    this.contentDiv._oldOverflow = null;
+                }
+            });
+        }
+
+        this.moveTo(px);
+        if (!this.autoSize && !this.size) {
+            this.setSize(this.contentSize);
+        }
+        this.setBackgroundColor();
+        this.setOpacity();
+        this.setBorder();
+        this.setContentHTML();
+        
+        if (this.panMapIfOutOfView) {
+            this.panIntoView();
+        }    
+
+        return this.div;
+    },
+
+    /** 
+     * Method: updatePosition
+     * if the popup has a lonlat and its map members set, 
+     * then have it move itself to its proper position
+     */
+    updatePosition: function() {
+        if ((this.lonlat) && (this.map)) {
+            var px = this.map.getLayerPxFromLonLat(this.lonlat);
+            if (px) {
+                this.moveTo(px);           
+            }    
+        }
+    },
+
+    /**
+     * Method: moveTo
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>} the top and left position of the popup div. 
+     */
+    moveTo: function(px) {
+        if ((px != null) && (this.div != null)) {
+            this.div.style.left = px.x + "px";
+            this.div.style.top = px.y + "px";
+        }
+    },
+
+    /**
+     * Method: visible
+     *
+     * Returns:      
+     * {Boolean} Boolean indicating whether or not the popup is visible
+     */
+    visible: function() {
+        return OpenLayers.Element.visible(this.div);
+    },
+
+    /**
+     * Method: toggle
+     * Toggles visibility of the popup.
+     */
+    toggle: function() {
+        if (this.visible()) {
+            this.hide();
+        } else {
+            this.show();
+        }
+    },
+
+    /**
+     * Method: show
+     * Makes the popup visible.
+     */
+    show: function() {
+        OpenLayers.Element.show(this.div);
+
+        if (this.panMapIfOutOfView) {
+            this.panIntoView();
+        }    
+    },
+
+    /**
+     * Method: hide
+     * Makes the popup invisible.
+     */
+    hide: function() {
+        OpenLayers.Element.hide(this.div);
+    },
+
+    /**
+     * Method: setSize
+     * Used to adjust the size of the popup. 
+     *
+     * Parameters:
+     * contentSize - {<OpenLayers.Size>} the new size for the popup's 
+     *     contents div (in pixels).
+     */
+    setSize:function(contentSize) { 
+        this.size = contentSize.clone(); 
+        
+        // if our contentDiv has a css 'padding' set on it by a stylesheet, we 
+        //  must add that to the desired "size". 
+        var contentDivPadding = this.getContentDivPadding();
+        var wPadding = contentDivPadding.left + contentDivPadding.right;
+        var hPadding = contentDivPadding.top + contentDivPadding.bottom;
+
+        // take into account the popup's 'padding' property
+        this.fixPadding();
+        wPadding += this.padding.left + this.padding.right;
+        hPadding += this.padding.top + this.padding.bottom;
+
+        // make extra space for the close div
+        if (this.closeDiv) {
+            var closeDivWidth = parseInt(this.closeDiv.style.width);
+            wPadding += closeDivWidth + contentDivPadding.right;
+        }
+
+        //increase size of the main popup div to take into account the 
+        // users's desired padding and close div.        
+        this.size.w += wPadding;
+        this.size.h += hPadding;
+
+        //now if our browser is IE, we need to actually make the contents 
+        // div itself bigger to take its own padding into effect. this makes 
+        // me want to shoot someone, but so it goes.
+        if (OpenLayers.Util.getBrowserName() == "msie") {
+            this.contentSize.w += 
+                contentDivPadding.left + contentDivPadding.right;
+            this.contentSize.h += 
+                contentDivPadding.bottom + contentDivPadding.top;
+        }
+
+        if (this.div != null) {
+            this.div.style.width = this.size.w + "px";
+            this.div.style.height = this.size.h + "px";
+        }
+        if (this.contentDiv != null){
+            this.contentDiv.style.width = contentSize.w + "px";
+            this.contentDiv.style.height = contentSize.h + "px";
+        }
+    },  
+
+    /**
+     * APIMethod: updateSize
+     * Auto size the popup so that it precisely fits its contents (as 
+     *     determined by this.contentDiv.innerHTML). Popup size will, of
+     *     course, be limited by the available space on the current map
+     */
+    updateSize: function() {
+        
+        // determine actual render dimensions of the contents by putting its
+        // contents into a fake contentDiv (for the CSS) and then measuring it
+        var preparedHTML = "<div class='" + this.contentDisplayClass+ "'>" + 
+            this.contentDiv.innerHTML + 
+            "<div>";
+        var realSize = OpenLayers.Util.getRenderedDimensions(
+            preparedHTML, null, { displayClass: this.displayClass }
+        );
+
+        // is the "real" size of the div is safe to display in our map?
+        var safeSize = this.getSafeContentSize(realSize);
+
+        var newSize = null;
+        if (safeSize.equals(realSize)) {
+            //real size of content is small enough to fit on the map, 
+            // so we use real size.
+            newSize = realSize;
+
+        } else {
+
+            //make a new OL.Size object with the clipped dimensions 
+            // set or null if not clipped.
+            var fixedSize = new OpenLayers.Size();
+            fixedSize.w = (safeSize.w < realSize.w) ? safeSize.w : null;
+            fixedSize.h = (safeSize.h < realSize.h) ? safeSize.h : null;
+        
+            if (fixedSize.w && fixedSize.h) {
+                //content is too big in both directions, so we will use 
+                // max popup size (safeSize), knowing well that it will 
+                // overflow both ways.                
+                newSize = safeSize;
+            } else {
+                //content is clipped in only one direction, so we need to 
+                // run getRenderedDimensions() again with a fixed dimension
+                var clippedSize = OpenLayers.Util.getRenderedDimensions(
+                    preparedHTML, fixedSize, 
+                    { displayClass: this.contentDisplayClass }
+                );
+                
+                //if the clipped size is still the same as the safeSize, 
+                // that means that our content must be fixed in the 
+                // offending direction. If overflow is 'auto', this means 
+                // we are going to have a scrollbar for sure, so we must 
+                // adjust for that.
+                //
+                var currentOverflow = OpenLayers.Element.getStyle(
+                    this.contentDiv, "overflow"
+                );
+                if ( (currentOverflow != "hidden") && 
+                     (clippedSize.equals(safeSize)) ) {
+                    var scrollBar = OpenLayers.Util.getScrollbarWidth();
+                    if (fixedSize.w) {
+                        clippedSize.h += scrollBar;
+                    } else {
+                        clippedSize.w += scrollBar;
+                    }
+                }
+                
+                newSize = this.getSafeContentSize(clippedSize);
+            }
+        }                        
+        this.setSize(newSize);     
+    },    
+
+    /**
+     * Method: setBackgroundColor
+     * Sets the background color of the popup.
+     *
+     * Parameters:
+     * color - {String} the background color.  eg "#FFBBBB"
+     */
+    setBackgroundColor:function(color) { 
+        if (color != undefined) {
+            this.backgroundColor = color; 
+        }
+        
+        if (this.div != null) {
+            this.div.style.backgroundColor = this.backgroundColor;
+        }
+    },  
+    
+    /**
+     * Method: setOpacity
+     * Sets the opacity of the popup.
+     * 
+     * Parameters:
+     * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).   
+     */
+    setOpacity:function(opacity) { 
+        if (opacity != undefined) {
+            this.opacity = opacity; 
+        }
+        
+        if (this.div != null) {
+            // for Mozilla and Safari
+            this.div.style.opacity = this.opacity;
+
+            // for IE
+            this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
+        }
+    },  
+    
+    /**
+     * Method: setBorder
+     * Sets the border style of the popup.
+     *
+     * Parameters:
+     * border - {String} The border style value. eg 2px 
+     */
+    setBorder:function(border) { 
+        if (border != undefined) {
+            this.border = border;
+        }
+        
+        if (this.div != null) {
+            this.div.style.border = this.border;
+        }
+    },      
+    
+    /**
+     * Method: setContentHTML
+     * Allows the user to set the HTML content of the popup.
+     *
+     * Parameters:
+     * contentHTML - {String} HTML for the div.
+     */
+    setContentHTML:function(contentHTML) {
+
+        if (contentHTML != null) {
+            this.contentHTML = contentHTML;
+        }
+       
+        if ((this.contentDiv != null) && 
+            (this.contentHTML != null) &&
+            (this.contentHTML != this.contentDiv.innerHTML)) {
+       
+            this.contentDiv.innerHTML = this.contentHTML;
+       
+            if (this.autoSize) {
+                
+                //if popup has images, listen for when they finish
+                // loading and resize accordingly
+                this.registerImageListeners();
+
+                //auto size the popup to its current contents
+                this.updateSize();
+            }
+        }    
+
+    },
+    
+    /**
+     * Method: registerImageListeners
+     * Called when an image contained by the popup loaded. this function
+     *     updates the popup size, then unregisters the image load listener.
+     */   
+    registerImageListeners: function() { 
+
+        // As the images load, this function will call updateSize() to 
+        // resize the popup to fit the content div (which presumably is now
+        // bigger than when the image was not loaded).
+        // 
+        // If the 'panMapIfOutOfView' property is set, we will pan the newly
+        // resized popup back into view.
+        // 
+        // Note that this function, when called, will have 'popup' and 
+        // 'img' properties in the context.
+        //
+        var onImgLoad = function() {
+            
+            this.popup.updateSize();
+     
+            if ( this.popup.visible() && this.popup.panMapIfOutOfView ) {
+                this.popup.panIntoView();
+            }
+
+            OpenLayers.Event.stopObserving(
+                this.img, "load", this.img._onImageLoad
+            );
+    
+        };
+
+        //cycle through the images and if their size is 0x0, that means that 
+        // they haven't been loaded yet, so we attach the listener, which 
+        // will fire when the images finish loading and will resize the 
+        // popup accordingly to its new size.
+        var images = this.contentDiv.getElementsByTagName("img");
+        for (var i = 0, len = images.length; i < len; i++) {
+            var img = images[i];
+            if (img.width == 0 || img.height == 0) {
+
+                var context = {
+                    'popup': this,
+                    'img': img
+                };
+
+                //expando this function to the image itself before registering
+                // it. This way we can easily and properly unregister it.
+                img._onImgLoad = OpenLayers.Function.bind(onImgLoad, context);
+
+                OpenLayers.Event.observe(img, 'load', img._onImgLoad);
+            }    
+        } 
+    },
+
+    /**
+     * APIMethod: getSafeContentSize
+     * 
+     * Parameters:
+     * size - {<OpenLayers.Size>} Desired size to make the popup.
+     * 
+     * Returns:
+     * {<OpenLayers.Size>} A size to make the popup which is neither smaller
+     *     than the specified minimum size, nor bigger than the maximum 
+     *     size (which is calculated relative to the size of the viewport).
+     */
+    getSafeContentSize: function(size) {
+
+        var safeContentSize = size.clone();
+
+        // if our contentDiv has a css 'padding' set on it by a stylesheet, we 
+        //  must add that to the desired "size". 
+        var contentDivPadding = this.getContentDivPadding();
+        var wPadding = contentDivPadding.left + contentDivPadding.right;
+        var hPadding = contentDivPadding.top + contentDivPadding.bottom;
+
+        // take into account the popup's 'padding' property
+        this.fixPadding();
+        wPadding += this.padding.left + this.padding.right;
+        hPadding += this.padding.top + this.padding.bottom;
+
+        if (this.closeDiv) {
+            var closeDivWidth = parseInt(this.closeDiv.style.width);
+            wPadding += closeDivWidth + contentDivPadding.right;
+        }
+
+        // prevent the popup from being smaller than a specified minimal size
+        if (this.minSize) {
+            safeContentSize.w = Math.max(safeContentSize.w, 
+                (this.minSize.w - wPadding));
+            safeContentSize.h = Math.max(safeContentSize.h, 
+                (this.minSize.h - hPadding));
+        }
+
+        // prevent the popup from being bigger than a specified maximum size
+        if (this.maxSize) {
+            safeContentSize.w = Math.min(safeContentSize.w, 
+                (this.maxSize.w - wPadding));
+            safeContentSize.h = Math.min(safeContentSize.h, 
+                (this.maxSize.h - hPadding));
+        }
+        
+        //make sure the desired size to set doesn't result in a popup that 
+        // is bigger than the map's viewport.
+        //
+        if (this.map && this.map.size) {
+
+            // Note that there *was* a reference to a
+            // 'OpenLayers.Popup.SCROLL_BAR_WIDTH' constant here, with special
+            // tolerance for it and everything... but it was never defined in
+            // the first place, so I don't know what to think.
+          
+            var maxY = this.map.size.h - 
+                this.map.paddingForPopups.top - 
+                this.map.paddingForPopups.bottom - 
+                hPadding;
+    
+            var maxX = this.map.size.w - 
+                this.map.paddingForPopups.left - 
+                this.map.paddingForPopups.right - 
+                wPadding;
+    
+            safeContentSize.w = Math.min(safeContentSize.w, maxX);
+            safeContentSize.h = Math.min(safeContentSize.h, maxY);
+        }
+        
+        return safeContentSize;
+    },
+    
+    /**
+     * Method: getContentDivPadding
+     * Glorious, oh glorious hack in order to determine the css 'padding' of 
+     *     the contentDiv. IE/Opera return null here unless we actually add the 
+     *     popup's main 'div' element (which contains contentDiv) to the DOM. 
+     *     So we make it invisible and then add it to the document temporarily. 
+     *
+     *     Once we've taken the padding readings we need, we then remove it 
+     *     from the DOM (it will actually get added to the DOM in 
+     *     Map.js's addPopup)
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>}
+     */
+    getContentDivPadding: function() {
+
+        //use cached value if we have it
+        var contentDivPadding = this._contentDivPadding;
+        if (!contentDivPadding) {
+            //make the div invisible and add it to the page        
+            this.div.style.display = "none";
+            document.body.appendChild(this.div);
+    
+            //read the padding settings from css, put them in an OL.Bounds        
+            contentDivPadding = new OpenLayers.Bounds(
+                OpenLayers.Element.getStyle(this.contentDiv, "padding-left"),
+                OpenLayers.Element.getStyle(this.contentDiv, "padding-bottom"),
+                OpenLayers.Element.getStyle(this.contentDiv, "padding-right"),
+                OpenLayers.Element.getStyle(this.contentDiv, "padding-top")
+            );
+    
+            //cache the value
+            this._contentDivPadding = contentDivPadding;
+    
+            //remove the div from the page and make it visible again
+            document.body.removeChild(this.div);
+            this.div.style.display = "";
+        }
+        return contentDivPadding;
+    },
+
+    /**
+     * Method: addCloseBox
+     * 
+     * Parameters:
+     * callback - {Function} The callback to be called when the close button
+     *     is clicked.
+     */
+    addCloseBox: function(callback) {
+
+        this.closeDiv = OpenLayers.Util.createDiv(
+            this.id + "_close", null, new OpenLayers.Size(17, 17)
+        );
+        this.closeDiv.className = "olPopupCloseBox"; 
+        
+        // use the content div's css padding to determine if we should
+        //  padd the close div
+        var contentDivPadding = this.getContentDivPadding();
+         
+        this.closeDiv.style.right = contentDivPadding.right + "px";
+        this.closeDiv.style.top = contentDivPadding.top + "px";
+        this.groupDiv.appendChild(this.closeDiv);
+
+        var closePopup = callback || function(e) {
+            this.hide();
+            OpenLayers.Event.stop(e);
+        };
+        OpenLayers.Event.observe(this.closeDiv, "click", 
+                OpenLayers.Function.bindAsEventListener(closePopup, this));
+    },
+
+    /**
+     * Method: panIntoView
+     * Pans the map such that the popup is totaly viewable (if necessary)
+     */
+    panIntoView: function() {
+        
+        var mapSize = this.map.getSize();
+    
+        //start with the top left corner of the popup, in px, 
+        // relative to the viewport
+        var origTL = this.map.getViewPortPxFromLayerPx( new OpenLayers.Pixel(
+            parseInt(this.div.style.left),
+            parseInt(this.div.style.top)
+        ));
+        var newTL = origTL.clone();
+    
+        //new left (compare to margins, using this.size to calculate right)
+        if (origTL.x < this.map.paddingForPopups.left) {
+            newTL.x = this.map.paddingForPopups.left;
+        } else 
+        if ( (origTL.x + this.size.w) > (mapSize.w - this.map.paddingForPopups.right)) {
+            newTL.x = mapSize.w - this.map.paddingForPopups.right - this.size.w;
+        }
+        
+        //new top (compare to margins, using this.size to calculate bottom)
+        if (origTL.y < this.map.paddingForPopups.top) {
+            newTL.y = this.map.paddingForPopups.top;
+        } else 
+        if ( (origTL.y + this.size.h) > (mapSize.h - this.map.paddingForPopups.bottom)) {
+            newTL.y = mapSize.h - this.map.paddingForPopups.bottom - this.size.h;
+        }
+        
+        var dx = origTL.x - newTL.x;
+        var dy = origTL.y - newTL.y;
+        
+        this.map.pan(dx, dy);
+    },
+
+    /** 
+     * Method: registerEvents
+     * Registers events on the popup.
+     *
+     * Do this in a separate function so that subclasses can 
+     *   choose to override it if they wish to deal differently
+     *   with mouse events
+     * 
+     *   Note in the following handler functions that some special
+     *    care is needed to deal correctly with mousing and popups. 
+     *   
+     *   Because the user might select the zoom-rectangle option and
+     *    then drag it over a popup, we need a safe way to allow the
+     *    mousemove and mouseup events to pass through the popup when
+     *    they are initiated from outside.
+     * 
+     *   Otherwise, we want to essentially kill the event propagation
+     *    for all other events, though we have to do so carefully, 
+     *    without disabling basic html functionality, like clicking on 
+     *    hyperlinks or drag-selecting text.
+     */
+     registerEvents:function() {
+        this.events = new OpenLayers.Events(this, this.div, null, true);
+
+        this.events.on({
+            "mousedown": this.onmousedown,
+            "mousemove": this.onmousemove,
+            "mouseup": this.onmouseup,
+            "click": this.onclick,
+            "mouseout": this.onmouseout,
+            "dblclick": this.ondblclick,
+            scope: this
+        });
+        
+     },
+
+    /** 
+     * Method: onmousedown 
+     * When mouse goes down within the popup, make a note of
+     *   it locally, and then do not propagate the mousedown 
+     *   (but do so safely so that user can select text inside)
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    onmousedown: function (evt) {
+        this.mousedown = true;
+        OpenLayers.Event.stop(evt, true);
+    },
+
+    /** 
+     * Method: onmousemove
+     * If the drag was started within the popup, then 
+     *   do not propagate the mousemove (but do so safely
+     *   so that user can select text inside)
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    onmousemove: function (evt) {
+        if (this.mousedown) {
+            OpenLayers.Event.stop(evt, true);
+        }
+    },
+
+    /** 
+     * Method: onmouseup
+     * When mouse comes up within the popup, after going down 
+     *   in it, reset the flag, and then (once again) do not 
+     *   propagate the event, but do so safely so that user can 
+     *   select text inside
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    onmouseup: function (evt) {
+        if (this.mousedown) {
+            this.mousedown = false;
+            OpenLayers.Event.stop(evt, true);
+        }
+    },
+
+    /**
+     * Method: onclick
+     * Ignore clicks, but allowing default browser handling
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    onclick: function (evt) {
+        OpenLayers.Event.stop(evt, true);
+    },
+
+    /** 
+     * Method: onmouseout
+     * When mouse goes out of the popup set the flag to false so that
+     *   if they let go and then drag back in, we won't be confused.
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    onmouseout: function (evt) {
+        this.mousedown = false;
+    },
+    
+    /** 
+     * Method: ondblclick
+     * Ignore double-clicks, but allowing default browser handling
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    ondblclick: function (evt) {
+        OpenLayers.Event.stop(evt, true);
+    },
+
+    CLASS_NAME: "OpenLayers.Popup"
+});
+
+OpenLayers.Popup.WIDTH = 200;
+OpenLayers.Popup.HEIGHT = 200;
+OpenLayers.Popup.COLOR = "white";
+OpenLayers.Popup.OPACITY = 1;
+OpenLayers.Popup.BORDER = "0px";
+/* ======================================================================
+    OpenLayers/Protocol.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Class: OpenLayers.Protocol
+ * Abstract vector layer protocol class.  Not to be instantiated directly.  Use
+ *     one of the protocol subclasses instead.
+ */
+OpenLayers.Protocol = OpenLayers.Class({
+    
+    /**
+     * Property: format
+     * {<OpenLayers.Format>}
+     */
+    format: null,
+    
+    /**
+     * Property: options
+     * Any options sent to the constructor.
+     */
+    options: null,
+
+    /**
+     * Property: autoDestroy
+     * {Boolean} The creator of the protocol can set autoDestroy to false
+     *      to fully control when the protocol is destroyed. Defaults to
+     *      true.
+     */
+    autoDestroy: true,
+   
+    /**
+     * Constructor: OpenLayers.Protocol
+     * Abstract class for vector protocols.  Create instances of a subclass.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     */
+    initialize: function(options) {
+        options = options || {};
+        OpenLayers.Util.extend(this, options);
+        this.options = options;
+    },
+
+    /**
+     * APIMethod: destroy
+     * Clean up the protocol.
+     */
+    destroy: function() {
+        this.options = null;
+        this.format = null;
+    },
+    
+    /**
+     * Method: read
+     * Construct a request for reading new features.
+     *
+     * Parameters:
+     * options - {Object} Optional object for configuring the request.
+     *
+     * Returns:
+     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+     * object, the same object will be passed to the callback function passed
+     * if one exists in the options object.
+     */
+    read: function() {
+    },
+    
+    
+    /**
+     * Method: create
+     * Construct a request for writing newly created features.
+     *
+     * Parameters:
+     * features - {Array({<OpenLayers.Feature.Vector>})} or
+     *            {<OpenLayers.Feature.Vector>}
+     * options - {Object} Optional object for configuring the request.
+     *
+     * Returns:
+     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+     * object, the same object will be passed to the callback function passed
+     * if one exists in the options object.
+     */
+    create: function() {
+    },
+    
+    /**
+     * Method: update
+     * Construct a request updating modified features.
+     *
+     * Parameters:
+     * features - {Array({<OpenLayers.Feature.Vector>})} or
+     *            {<OpenLayers.Feature.Vector>}
+     * options - {Object} Optional object for configuring the request.
+     *
+     * Returns:
+     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+     * object, the same object will be passed to the callback function passed
+     * if one exists in the options object.
+     */
+    update: function() {
+    },
+    
+    /**
+     * Method: delete
+     * Construct a request deleting a removed feature.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     * options - {Object} Optional object for configuring the request.
+     *
+     * Returns:
+     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+     * object, the same object will be passed to the callback function passed
+     * if one exists in the options object.
+     */
+    "delete": function() {
+    },
+
+    /**
+     * Method: commit
+     * Go over the features and for each take action
+     * based on the feature state. Possible actions are create,
+     * update and delete.
+     *
+     * Parameters:
+     * features - {Array({<OpenLayers.Feature.Vector>})}
+     * options - {Object} Object whose possible keys are "create", "update",
+     *      "delete", "callback" and "scope", the values referenced by the
+     *      first three are objects as passed to the "create", "update", and
+     *      "delete" methods, the value referenced by the "callback" key is
+     *      a function which is called when the commit operation is complete
+     *      using the scope referenced by the "scope" key.
+     *
+     * Returns:
+     * {Array({<OpenLayers.Protocol.Response>})} An array of
+     * <OpenLayers.Protocol.Response> objects.
+     */
+    commit: function() {
+    },
+   
+    CLASS_NAME: "OpenLayers.Protocol" 
+});
+
+/**
+ * Class: OpenLayers.Protocol.Response
+ * Protocols return Response objects to their users.
+ */
+OpenLayers.Protocol.Response = OpenLayers.Class({
+    /**
+     * Property: code
+     * {Number} - OpenLayers.Protocol.Response.SUCCESS or
+     *            OpenLayers.Protocol.Response.FAILURE
+     */
+    code: null,
+
+    /**
+     * Property: requestType
+     * {String} The type of request this response corresponds to. Either
+     *      "create", "read", "update" or "delete".
+     */
+    requestType: null,
+
+    /**
+     * Property: last
+     * {Boolean} - true if this is the last response expected in a commit,
+     * false otherwise, defaults to true.
+     */
+    last: true,
+
+    /**
+     * Property: features
+     * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+     * The features returned in the response by the server.
+     */
+    features: null,
+
+    /**
+     * Property: reqFeatures
+     * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+     * The features provided by the user and placed in the request by the
+     *      protocol.
+     */
+    reqFeatures: null,
+
+    /**
+     * Property: priv
+     */
+    priv: null,
+
+    /**
+     * Constructor: OpenLayers.Protocol.Response
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /**
+     * Method: success
+     *
+     * Returns:
+     * {Boolean} - true on success, false otherwise
+     */
+    success: function() {
+        return this.code > 0;
+    },
+
+    CLASS_NAME: "OpenLayers.Protocol.Response"
+});
+
+OpenLayers.Protocol.Response.SUCCESS = 1;
+OpenLayers.Protocol.Response.FAILURE = 0;
+/* ======================================================================
+    OpenLayers/Renderer.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Class: OpenLayers.Renderer 
+ * This is the base class for all renderers.
+ *
+ * This is based on a merger code written by Paul Spencer and Bertil Chapuis.
+ * It is largely composed of virtual functions that are to be implemented
+ * in technology-specific subclasses, but there is some generic code too.
+ * 
+ * The functions that *are* implemented here merely deal with the maintenance
+ *  of the size and extent variables, as well as the cached 'resolution' 
+ *  value. 
+ * 
+ * A note to the user that all subclasses should use getResolution() instead
+ *  of directly accessing this.resolution in order to correctly use the 
+ *  cacheing system.
+ *
+ */
+OpenLayers.Renderer = OpenLayers.Class({
+
+    /** 
+     * Property: container
+     * {DOMElement} 
+     */
+    container: null,
+    
+    /** 
+     * Property: extent
+     * {<OpenLayers.Bounds>}
+     */
+    extent: null,
+
+    /**
+     * Property: locked
+     * {Boolean} If the renderer is currently in a state where many things
+     *     are changing, the 'locked' property is set to true. This means 
+     *     that renderers can expect at least one more drawFeature event to be
+     *     called with the 'locked' property set to 'true': In some renderers,
+     *     this might make sense to use as a 'only update local information'
+     *     flag. 
+     */  
+    locked: false,
+    
+    /** 
+     * Property: size
+     * {<OpenLayers.Size>} 
+     */
+    size: null,
+    
+    /**
+     * Property: resolution
+     * {Float} cache of current map resolution
+     */
+    resolution: null,
+    
+    /**
+     * Property: map  
+     * {<OpenLayers.Map>} Reference to the map -- this is set in Vector's setMap()
+     */
+    map: null,
+    
+    /**
+     * Constructor: OpenLayers.Renderer 
+     *
+     * Parameters:
+     * containerID - {<String>} 
+     * options - {Object} options for this renderer. See sublcasses for
+     *     supported options.
+     */
+    initialize: function(containerID, options) {
+        this.container = OpenLayers.Util.getElement(containerID);
+    },
+    
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        this.container = null;
+        this.extent = null;
+        this.size =  null;
+        this.resolution = null;
+        this.map = null;
+    },
+
+    /**
+     * APIMethod: supported
+     * This should be overridden by specific subclasses
+     * 
+     * Returns:
+     * {Boolean} Whether or not the browser supports the renderer class
+     */
+    supported: function() {
+        return false;
+    },    
+    
+    /**
+     * Method: setExtent
+     * Set the visible part of the layer.
+     *
+     * Resolution has probably changed, so we nullify the resolution 
+     * cache (this.resolution) -- this way it will be re-computed when 
+     * next it is needed.
+     * We nullify the resolution cache (this.resolution) if resolutionChanged
+     * is set to true - this way it will be re-computed on the next
+     * getResolution() request.
+     *
+     * Parameters:
+     * extent - {<OpenLayers.Bounds>}
+     * resolutionChanged - {Boolean}
+     */
+    setExtent: function(extent, resolutionChanged) {
+        this.extent = extent.clone();
+        if (resolutionChanged) {
+            this.resolution = null;
+        }
+    },
+    
+    /**
+     * Method: setSize
+     * Sets the size of the drawing surface.
+     * 
+     * Resolution has probably changed, so we nullify the resolution 
+     * cache (this.resolution) -- this way it will be re-computed when 
+     * next it is needed.
+     *
+     * Parameters:
+     * size - {<OpenLayers.Size>} 
+     */
+    setSize: function(size) {
+        this.size = size.clone();
+        this.resolution = null;
+    },
+    
+    /** 
+     * Method: getResolution
+     * Uses cached copy of resolution if available to minimize computing
+     * 
+     * Returns:
+     * The current map's resolution
+     */
+    getResolution: function() {
+        this.resolution = this.resolution || this.map.getResolution();
+        return this.resolution;
+    },
+    
+    /**
+     * Method: drawFeature
+     * Draw the feature.  The optional style argument can be used
+     * to override the feature's own style.  This method should only
+     * be called from layer.drawFeature().
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     * style - {<Object>}
+     * 
+     * Returns:
+     * {Boolean} true if the feature has been drawn completely, false if not,
+     *     undefined if the feature had no geometry
+     */
+    drawFeature: function(feature, style) {
+        if(style == null) {
+            style = feature.style;
+        }
+        if (feature.geometry) {
+            if (!feature.geometry.getBounds().intersectsBounds(this.extent)) {
+                style = {display: "none"};
+            }
+            return this.drawGeometry(feature.geometry, style, feature.id);
+        }
+    },
+
+
+    /** 
+     * Method: drawGeometry
+     * 
+     * Draw a geometry.  This should only be called from the renderer itself.
+     * Use layer.drawFeature() from outside the renderer.
+     * virtual function
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} 
+     * style - {Object} 
+     * featureId - {<String>} 
+     */
+    drawGeometry: function(geometry, style, featureId) {},
+        
+    /**
+     * Method: clear
+     * Clear all vectors from the renderer.
+     * virtual function.
+     */    
+    clear: function() {},
+
+    /**
+     * Method: getFeatureIdFromEvent
+     * Returns a feature id from an event on the renderer.  
+     * How this happens is specific to the renderer.  This should be
+     * called from layer.getFeatureFromEvent().
+     * Virtual function.
+     * 
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     *
+     * Returns:
+     * {String} A feature id or null.
+     */
+    getFeatureIdFromEvent: function(evt) {},
+    
+    /**
+     * Method: eraseFeatures 
+     * This is called by the layer to erase features
+     * 
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    eraseFeatures: function(features) {
+        if(!(features instanceof Array)) {
+            features = [features];
+        }
+        for(var i=0, len=features.length; i<len; ++i) {
+            this.eraseGeometry(features[i].geometry);
+        }
+    },
+    
+    /**
+     * Method: eraseGeometry
+     * Remove a geometry from the renderer (by id).
+     * virtual function.
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} 
+     */
+    eraseGeometry: function(geometry) {},
+
+    CLASS_NAME: "OpenLayers.Renderer"
+});
+/* ======================================================================
+    OpenLayers/Request.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Namespace: OpenLayers.Request
+ * The OpenLayers.Request namespace contains convenience methods for working
+ *     with XMLHttpRequests.  These methods work with a cross-browser
+ *     W3C compliant <OpenLayers.Request.XMLHttpRequest> class.
+ */
+OpenLayers.Request = {
+    
+    /**
+     * Constant: DEFAULT_CONFIG
+     * {Object} Default configuration for all requests.
+     */
+    DEFAULT_CONFIG: {
+        method: "GET",
+        url: window.location.href,
+        async: true,
+        user: undefined,
+        password: undefined,
+        params: null,
+        proxy: OpenLayers.ProxyHost,
+        headers: {},
+        data: null,
+        callback: function() {},
+        success: null,
+        failure: null,
+        scope: null
+    },
+    
+    /**
+     * APIMethod: issue
+     * Create a new XMLHttpRequest object, open it, set any headers, bind
+     *     a callback to done state, and send any data.
+     *
+     * Parameters:
+     * config - {Object} Object containing properties for configuring the
+     *     request.  Allowed configuration properties are described below.
+     *     This object is modified and should not be reused.
+     *
+     * Allowed config properties:
+     * method - {String} One of GET, POST, PUT, DELETE, HEAD, or
+     *     OPTIONS.  Default is GET.
+     * url - {String} URL for the request.
+     * async - {Boolean} Open an asynchronous request.  Default is true.
+     * user - {String} User for relevant authentication scheme.  Set
+     *     to null to clear current user.
+     * password - {String} Password for relevant authentication scheme.
+     *     Set to null to clear current password.
+     * proxy - {String} Optional proxy.  Defaults to
+     *     <OpenLayers.ProxyHost>.
+     * params - {Object} Any key:value pairs to be appended to the
+     *     url as a query string.  Assumes url doesn't already include a query
+     *     string or hash.  Parameter values that are arrays will be
+     *     concatenated with a comma (note that this goes against form-encoding)
+     *     as is done with <OpenLayers.Util.getParameterString>.
+     * headers - {Object} Object with header:value pairs to be set on
+     *     the request.
+     * data - {Object} Any data to send with the request.
+     * callback - {Function} Function to call when request is done.
+     *     To determine if the request failed, check request.status (200
+     *     indicates success).
+     * success - {Function} Optional function to call if request status is in
+     *     the 200s.  This will be called in addition to callback above and
+     *     would typically only be used as an alternative.
+     * failure - {Function} Optional function to call if request status is not
+     *     in the 200s.  This will be called in addition to callback above and
+     *     would typically only be used as an alternative.
+     * scope - {Object} If callback is a public method on some object,
+     *     set the scope to that object.
+     *
+     * Returns:
+     * {XMLHttpRequest} Request object.  To abort the request before a response
+     *     is received, call abort() on the request object.
+     */
+    issue: function(config) {        
+        // apply default config - proxy host may have changed
+        var defaultConfig = OpenLayers.Util.extend(
+            this.DEFAULT_CONFIG,
+            {proxy: OpenLayers.ProxyHost}
+        );
+        config = OpenLayers.Util.applyDefaults(config, defaultConfig);
+
+        // create request, open, and set headers
+        var request = new OpenLayers.Request.XMLHttpRequest();
+        var url = config.url;
+        if(config.params) {
+            url += "?" + OpenLayers.Util.getParameterString(config.params);
+        }
+        if(config.proxy && (url.indexOf("http") == 0)) {
+            url = config.proxy + encodeURIComponent(url);
+        }
+        request.open(
+            config.method, url, config.async, config.user, config.password
+        );
+        for(var header in config.headers) {
+            request.setRequestHeader(header, config.headers[header]);
+        }
+
+        // bind callbacks to readyState 4 (done)
+        var complete = (config.scope) ?
+            OpenLayers.Function.bind(config.callback, config.scope) :
+            config.callback;
+        
+        // optional success callback
+        var success;
+        if(config.success) {
+            success = (config.scope) ?
+                OpenLayers.Function.bind(config.success, config.scope) :
+                config.success;
+        }
+
+        // optional failure callback
+        var failure;
+        if(config.failure) {
+            failure = (config.scope) ?
+                OpenLayers.Function.bind(config.failure, config.scope) :
+                config.failure;
+        }
+         
+        request.onreadystatechange = function() {
+            if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) {
+                complete(request);
+                if(success && (!request.status ||
+                   (request.status >= 200 && request.status < 300))) {
+                    success(request);
+                }
+                if(failure && (request.status &&
+                   (request.status < 200 || request.status >= 300))) {
+                    failure(request);
+                }
+            }
+        }
+        
+        // send request (optionally with data) and return
+        request.send(config.data);
+        return request;
+    },
+    
+    /**
+     * APIMethod: GET
+     * Send an HTTP GET request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to GET.
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.
+     *     This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    GET: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "GET"});
+        return OpenLayers.Request.issue(config);
+    },
+    
+    /**
+     * APIMethod: POST
+     * Send a POST request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to POST and "Content-Type" header set to "application/xml".
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.  The
+     *     default "Content-Type" header will be set to "application-xml" if
+     *     none is provided.  This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    POST: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "POST"});
+        // set content type to application/xml if it isn't already set
+        config.headers = config.headers ? config.headers : {};
+        if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+            config.headers["Content-Type"] = "application/xml";
+        }
+        return OpenLayers.Request.issue(config);
+    },
+    
+    /**
+     * APIMethod: PUT
+     * Send an HTTP PUT request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to PUT and "Content-Type" header set to "application/xml".
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.  The
+     *     default "Content-Type" header will be set to "application-xml" if
+     *     none is provided.  This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    PUT: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "PUT"});
+        // set content type to application/xml if it isn't already set
+        config.headers = config.headers ? config.headers : {};
+        if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+            config.headers["Content-Type"] = "application/xml";
+        }
+        return OpenLayers.Request.issue(config);
+    },
+    
+    /**
+     * APIMethod: DELETE
+     * Send an HTTP DELETE request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to DELETE.
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.
+     *     This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    DELETE: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "DELETE"});
+        return OpenLayers.Request.issue(config);
+    },
+  
+    /**
+     * APIMethod: HEAD
+     * Send an HTTP HEAD request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to HEAD.
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.
+     *     This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    HEAD: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "HEAD"});
+        return OpenLayers.Request.issue(config);
+    },
+    
+    /**
+     * APIMethod: OPTIONS
+     * Send an HTTP OPTIONS request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to OPTIONS.
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.
+     *     This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    OPTIONS: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "OPTIONS"});
+        return OpenLayers.Request.issue(config);
+    }
+
+};
+/* ======================================================================
+    OpenLayers/Strategy.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Class: OpenLayers.Strategy
+ * Abstract vector layer strategy class.  Not to be instantiated directly.  Use
+ *     one of the strategy subclasses instead.
+ */
+OpenLayers.Strategy = OpenLayers.Class({
+    
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>}
+     */
+    layer: null,
+    
+    /**
+     * Property: options
+     * {Object} Any options sent to the constructor.
+     */
+    options: null,
+
+    /** 
+     * Property: active 
+     * {Boolean} The control is active.
+     */
+    active: null,
+
+    /**
+     * Property: autoActivate
+     * {Boolean} The creator of the strategy can set autoActivate to false
+     *      to fully control when the protocol is activated and deactivated.
+     *      Defaults to true.
+     */
+    autoActivate: true,
+
+    /**
+     * Property: autoDestroy
+     * {Boolean} The creator of the strategy can set autoDestroy to false
+     *      to fully control when the strategy is destroyed. Defaults to
+     *      true.
+     */
+    autoDestroy: true,
+
+    /**
+     * Constructor: OpenLayers.Strategy
+     * Abstract class for vector strategies.  Create instances of a subclass.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Util.extend(this, options);
+        this.options = options;
+        // set the active property here, so that user cannot override it
+        this.active = false;
+    },
+    
+    /**
+     * APIMethod: destroy
+     * Clean up the strategy.
+     */
+    destroy: function() {
+        this.deactivate();
+        this.layer = null;
+        this.options = null;
+    },
+
+    /**
+     * Method: setLayer.
+     *
+     * Parameters:
+     * {<OpenLayers.Layer.Vector>}
+     */
+    setLayer: function(layer) {
+        this.layer = layer;
+    },
+    
+    /**
+     * Method: activate
+     * Activate the strategy.  Register any listeners, do appropriate setup.
+     *
+     * Returns:
+     * {Boolean} True if the strategy was successfully activated or false if
+     *      the strategy was already active.
+     */
+    activate: function() {
+        if (!this.active) {
+            this.active = true;
+            return true;
+        }
+        return false;
+    },
+    
+    /**
+     * Method: deactivate
+     * Deactivate the strategy.  Unregister any listeners, do appropriate
+     *     tear-down.
+     *
+     * Returns:
+     * {Boolean} True if the strategy was successfully deactivated or false if
+     *      the strategy was already inactive.
+     */
+    deactivate: function() {
+        if (this.active) {
+            this.active = false;
+            return true;
+        }
+        return false;
+    },
+   
+    CLASS_NAME: "OpenLayers.Strategy" 
+});
+/* ======================================================================
     OpenLayers/Tween.js
    ====================================================================== */
 
@@ -5422,6 +7131,254 @@
     CLASS_NAME: "OpenLayers.Easing.Quad"
 };
 /* ======================================================================
+    Rico/Color.js
+   ====================================================================== */
+
+/*
+ * This file has been edited substantially from the Rico-released version by
+ * the OpenLayers development team.
+ *
+ * This file is licensed under the Apache License, Version 2.0.
+ */
+OpenLayers.Rico.Color = OpenLayers.Class({
+
+   initialize: function(red, green, blue) {
+      this.rgb = { r: red, g : green, b : blue };
+   },
+
+   setRed: function(r) {
+      this.rgb.r = r;
+   },
+
+   setGreen: function(g) {
+      this.rgb.g = g;
+   },
+
+   setBlue: function(b) {
+      this.rgb.b = b;
+   },
+
+   setHue: function(h) {
+
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.h = h;
+
+      // convert back to RGB...
+      this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+   },
+
+   setSaturation: function(s) {
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.s = s;
+
+      // convert back to RGB and set values...
+      this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+   },
+
+   setBrightness: function(b) {
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.b = b;
+
+      // convert back to RGB and set values...
+      this.rgb = OpenLayers.Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
+   },
+
+   darken: function(percent) {
+      var hsb  = this.asHSB();
+      this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
+   },
+
+   brighten: function(percent) {
+      var hsb  = this.asHSB();
+      this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
+   },
+
+   blend: function(other) {
+      this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
+      this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
+      this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
+   },
+
+   isBright: function() {
+      var hsb = this.asHSB();
+      return this.asHSB().b > 0.5;
+   },
+
+   isDark: function() {
+      return ! this.isBright();
+   },
+
+   asRGB: function() {
+      return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
+   },
+
+   asHex: function() {
+      return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
+   },
+
+   asHSB: function() {
+      return OpenLayers.Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
+   },
+
+   toString: function() {
+      return this.asHex();
+   }
+
+});
+
+OpenLayers.Rico.Color.createFromHex = function(hexCode) {
+  if(hexCode.length==4) {
+    var shortHexCode = hexCode; 
+    var hexCode = '#';
+    for(var i=1;i<4;i++) { 
+        hexCode += (shortHexCode.charAt(i) + 
+shortHexCode.charAt(i)); 
+    }
+  }
+   if ( hexCode.indexOf('#') == 0 ) {
+       hexCode = hexCode.substring(1);
+   }
+   var red   = hexCode.substring(0,2);
+   var green = hexCode.substring(2,4);
+   var blue  = hexCode.substring(4,6);
+   return new OpenLayers.Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
+};
+
+/**
+ * Factory method for creating a color from the background of
+ * an HTML element.
+ */
+OpenLayers.Rico.Color.createColorFromBackground = function(elem) {
+
+   var actualColor = 
+      RicoUtil.getElementsComputedStyle(OpenLayers.Util.getElement(elem), 
+                                        "backgroundColor", 
+                                        "background-color");
+
+   if ( actualColor == "transparent" && elem.parentNode ) {
+      return OpenLayers.Rico.Color.createColorFromBackground(elem.parentNode);
+   }
+   if ( actualColor == null ) {
+      return new OpenLayers.Rico.Color(255,255,255);
+   }
+   if ( actualColor.indexOf("rgb(") == 0 ) {
+      var colors = actualColor.substring(4, actualColor.length - 1 );
+      var colorArray = colors.split(",");
+      return new OpenLayers.Rico.Color( parseInt( colorArray[0] ),
+                            parseInt( colorArray[1] ),
+                            parseInt( colorArray[2] )  );
+
+   }
+   else if ( actualColor.indexOf("#") == 0 ) {
+      return OpenLayers.Rico.Color.createFromHex(actualColor);
+   }
+   else {
+      return new OpenLayers.Rico.Color(255,255,255);
+   }
+};
+
+OpenLayers.Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
+
+   var red   = 0;
+    var green = 0;
+    var blue  = 0;
+
+   if (saturation == 0) {
+      red = parseInt(brightness * 255.0 + 0.5);
+       green = red;
+       blue = red;
+    }
+    else {
+      var h = (hue - Math.floor(hue)) * 6.0;
+      var f = h - Math.floor(h);
+      var p = brightness * (1.0 - saturation);
+      var q = brightness * (1.0 - saturation * f);
+      var t = brightness * (1.0 - (saturation * (1.0 - f)));
+
+      switch (parseInt(h)) {
+         case 0:
+            red   = (brightness * 255.0 + 0.5);
+            green = (t * 255.0 + 0.5);
+            blue  = (p * 255.0 + 0.5);
+            break;
+         case 1:
+            red   = (q * 255.0 + 0.5);
+            green = (brightness * 255.0 + 0.5);
+            blue  = (p * 255.0 + 0.5);
+            break;
+         case 2:
+            red   = (p * 255.0 + 0.5);
+            green = (brightness * 255.0 + 0.5);
+            blue  = (t * 255.0 + 0.5);
+            break;
+         case 3:
+            red   = (p * 255.0 + 0.5);
+            green = (q * 255.0 + 0.5);
+            blue  = (brightness * 255.0 + 0.5);
+            break;
+         case 4:
+            red   = (t * 255.0 + 0.5);
+            green = (p * 255.0 + 0.5);
+            blue  = (brightness * 255.0 + 0.5);
+            break;
+          case 5:
+            red   = (brightness * 255.0 + 0.5);
+            green = (p * 255.0 + 0.5);
+            blue  = (q * 255.0 + 0.5);
+            break;
+        }
+    }
+
+   return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
+};
+
+OpenLayers.Rico.Color.RGBtoHSB = function(r, g, b) {
+
+   var hue;
+   var saturation;
+   var brightness;
+
+   var cmax = (r > g) ? r : g;
+   if (b > cmax) {
+      cmax = b;
+   }
+   var cmin = (r < g) ? r : g;
+   if (b < cmin) {
+      cmin = b;
+   }
+   brightness = cmax / 255.0;
+   if (cmax != 0) {
+      saturation = (cmax - cmin)/cmax;
+   } else {
+      saturation = 0;
+   }
+   if (saturation == 0) {
+      hue = 0;
+   } else {
+      var redc   = (cmax - r)/(cmax - cmin);
+        var greenc = (cmax - g)/(cmax - cmin);
+        var bluec  = (cmax - b)/(cmax - cmin);
+
+        if (r == cmax) {
+           hue = bluec - greenc;
+        } else if (g == cmax) {
+           hue = 2.0 + redc - bluec;
+      } else {
+           hue = 4.0 + greenc - redc;
+      }
+        hue = hue / 6.0;
+        if (hue < 0) {
+           hue = hue + 1.0;
+        }
+   }
+
+   return { h : hue, s : saturation, b : brightness };
+};
+
+/* ======================================================================
     OpenLayers/Control/ArgParser.js
    ====================================================================== */
 
@@ -5495,7 +7452,7 @@
         OpenLayers.Control.prototype.setMap.apply(this, arguments);
 
         //make sure we dont already have an arg parser attached
-        for(var i=0; i< this.map.controls.length; i++) {
+        for(var i=0, len=this.map.controls.length; i<len; i++) {
             var control = this.map.controls[i];
             if ( (control != this) &&
                  (control.CLASS_NAME == "OpenLayers.Control.ArgParser") ) {
@@ -5568,7 +7525,7 @@
         if (this.layers.length == this.map.layers.length) { 
             this.map.events.unregister('addlayer', this, this.configureLayers);
 
-            for(var i=0; i < this.layers.length; i++) {
+            for(var i=0, len=this.layers.length; i<len; i++) {
                 
                 var layer = this.map.layers[i];
                 var c = this.layers.charAt(i);
@@ -5585,6 +7542,1145 @@
     CLASS_NAME: "OpenLayers.Control.ArgParser"
 });
 /* ======================================================================
+    OpenLayers/Control/Attribution.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Attribution
+ * Add attribution from layers to the map display. Uses 'attribution' property
+ * of each layer.
+ */
+OpenLayers.Control.Attribution = 
+  OpenLayers.Class(OpenLayers.Control, {
+    
+    /**
+     * APIProperty: seperator
+     * {String} String used to seperate layers.
+     */
+    separator: ", ",
+       
+    /**
+     * Constructor: OpenLayers.Control.Attribution 
+     * 
+     * Parameters:
+     * options - {Object} Options for control.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, arguments);
+    },
+
+    /** 
+     * Method: destroy
+     * Destroy control.
+     */
+    destroy: function() {
+        this.map.events.un({
+            "removelayer": this.updateAttribution,
+            "addlayer": this.updateAttribution,
+            "changelayer": this.updateAttribution,
+            "changebaselayer": this.updateAttribution,
+            scope: this
+        });
+        
+        OpenLayers.Control.prototype.destroy.apply(this, arguments);
+    },    
+    
+    /**
+     * Method: draw
+     * Initialize control.
+     * 
+     * Returns: 
+     * {DOMElement} A reference to the DIV DOMElement containing the control
+     */    
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        
+        this.map.events.on({
+            'changebaselayer': this.updateAttribution,
+            'changelayer': this.updateAttribution,
+            'addlayer': this.updateAttribution,
+            'removelayer': this.updateAttribution,
+            scope: this
+        });
+        this.updateAttribution();
+        
+        return this.div;    
+    },
+
+    /**
+     * Method: updateAttribution
+     * Update attribution string.
+     */
+    updateAttribution: function() {
+        var attributions = [];
+        if (this.map && this.map.layers) {
+            for(var i=0, len=this.map.layers.length; i<len; i++) {
+                var layer = this.map.layers[i];
+                if (layer.attribution && layer.getVisibility()) {
+                    attributions.push( layer.attribution );
+                }
+            }  
+            this.div.innerHTML = attributions.join(this.separator);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Control.Attribution"
+});
+/* ======================================================================
+    OpenLayers/Control/Button.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Button 
+ * A very simple button controlfor use with <OpenLayers.Control.Panel>.
+ * When clicked, the function trigger() is executed.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ *
+ * Use:
+ * (code)
+ * var button = new OpenLayers.Control.Button({
+ *     displayClass: "MyButton", trigger: myFunction
+ * });
+ * panel.addControls([button]);
+ * (end)
+ * 
+ * Will create a button with CSS class MyButtonItemInactive, that
+ *     will call the function MyFunction() when clicked.
+ */
+OpenLayers.Control.Button = OpenLayers.Class(OpenLayers.Control, {
+    /**
+     * Property: type
+     * {Integer} OpenLayers.Control.TYPE_BUTTON.
+     */
+    type: OpenLayers.Control.TYPE_BUTTON,
+    
+    /**
+     * Method: trigger
+     * Called by a control panel when the button is clicked.
+     */
+    trigger: function() {},
+
+    CLASS_NAME: "OpenLayers.Control.Button"
+});
+/* ======================================================================
+    OpenLayers/Control/LayerSwitcher.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/** 
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.LayerSwitcher
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.LayerSwitcher = 
+  OpenLayers.Class(OpenLayers.Control, {
+
+    /**  
+     * Property: activeColor
+     * {String}
+     */
+    activeColor: "darkblue",
+    
+    /**  
+     * Property: layerStates 
+     * {Array(Object)} Basically a copy of the "state" of the map's layers 
+     *     the last time the control was drawn. We have this in order to avoid
+     *     unnecessarily redrawing the control.
+     */
+    layerStates: null,
+    
+
+  // DOM Elements
+  
+    /**
+     * Property: layersDiv
+     * {DOMElement} 
+     */
+    layersDiv: null,
+    
+    /** 
+     * Property: baseLayersDiv
+     * {DOMElement}
+     */
+    baseLayersDiv: null,
+
+    /** 
+     * Property: baseLayers
+     * {Array(<OpenLayers.Layer>)}
+     */
+    baseLayers: null,
+    
+    
+    /** 
+     * Property: dataLbl
+     * {DOMElement} 
+     */
+    dataLbl: null,
+    
+    /** 
+     * Property: dataLayersDiv
+     * {DOMElement} 
+     */
+    dataLayersDiv: null,
+
+    /** 
+     * Property: dataLayers
+     * {Array(<OpenLayers.Layer>)} 
+     */
+    dataLayers: null,
+
+
+    /** 
+     * Property: minimizeDiv
+     * {DOMElement} 
+     */
+    minimizeDiv: null,
+
+    /** 
+     * Property: maximizeDiv
+     * {DOMElement} 
+     */
+    maximizeDiv: null,
+    
+    /**
+     * APIProperty: ascending
+     * {Boolean} 
+     */
+    ascending: true,
+ 
+    /**
+     * Constructor: OpenLayers.Control.LayerSwitcher
+     * 
+     * Parameters:
+     * options - {Object}
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, arguments);
+        this.layerStates = [];
+    },
+
+    /**
+     * APIMethod: destroy 
+     */    
+    destroy: function() {
+        
+        OpenLayers.Event.stopObservingElement(this.div);
+
+        OpenLayers.Event.stopObservingElement(this.minimizeDiv);
+        OpenLayers.Event.stopObservingElement(this.maximizeDiv);
+
+        //clear out layers info and unregister their events 
+        this.clearLayersArray("base");
+        this.clearLayersArray("data");
+        
+        this.map.events.un({
+            "addlayer": this.redraw,
+            "changelayer": this.redraw,
+            "removelayer": this.redraw,
+            "changebaselayer": this.redraw,
+            scope: this
+        });
+        
+        OpenLayers.Control.prototype.destroy.apply(this, arguments);
+    },
+
+    /** 
+     * Method: setMap
+     *
+     * Properties:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+        this.map.events.on({
+            "addlayer": this.redraw,
+            "changelayer": this.redraw,
+            "removelayer": this.redraw,
+            "changebaselayer": this.redraw,
+            scope: this
+        });
+    },
+
+    /**
+     * Method: draw
+     *
+     * Returns:
+     * {DOMElement} A reference to the DIV DOMElement containing the 
+     *     switcher tabs.
+     */  
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this);
+
+        // create layout divs
+        this.loadContents();
+
+        // set mode to minimize
+        if(!this.outsideViewport) {
+            this.minimizeControl();
+        }
+
+        // populate div with current info
+        this.redraw();    
+
+        return this.div;
+    },
+
+    /** 
+     * Method: clearLayersArray
+     * User specifies either "base" or "data". we then clear all the
+     *     corresponding listeners, the div, and reinitialize a new array.
+     * 
+     * Parameters:
+     * layersType - {String}  
+     */
+    clearLayersArray: function(layersType) {
+        var layers = this[layersType + "Layers"];
+        if (layers) {
+            for(var i=0, len=layers.length; i<len ; i++) {
+                var layer = layers[i];
+                OpenLayers.Event.stopObservingElement(layer.inputElem);
+                OpenLayers.Event.stopObservingElement(layer.labelSpan);
+            }
+        }
+        this[layersType + "LayersDiv"].innerHTML = "";
+        this[layersType + "Layers"] = [];
+    },
+
+
+    /**
+     * Method: checkRedraw
+     * Checks if the layer state has changed since the last redraw() call.
+     * 
+     * Returns:
+     * {Boolean} The layer state changed since the last redraw() call. 
+     */
+    checkRedraw: function() {
+        var redraw = false;
+        if ( !this.layerStates.length ||
+             (this.map.layers.length != this.layerStates.length) ) {
+            redraw = true;
+        } else {
+            for (var i=0, len=this.layerStates.length; i<len; i++) {
+                var layerState = this.layerStates[i];
+                var layer = this.map.layers[i];
+                if ( (layerState.name != layer.name) || 
+                     (layerState.inRange != layer.inRange) || 
+                     (layerState.id != layer.id) || 
+                     (layerState.visibility != layer.visibility) ) {
+                    redraw = true;
+                    break;
+                }    
+            }
+        }    
+        return redraw;
+    },
+    
+    /** 
+     * Method: redraw
+     * Goes through and takes the current state of the Map and rebuilds the
+     *     control to display that state. Groups base layers into a 
+     *     radio-button group and lists each data layer with a checkbox.
+     *
+     * Returns: 
+     * {DOMElement} A reference to the DIV DOMElement containing the control
+     */  
+    redraw: function() {
+        //if the state hasn't changed since last redraw, no need 
+        // to do anything. Just return the existing div.
+        if (!this.checkRedraw()) { 
+            return this.div; 
+        } 
+
+        //clear out previous layers 
+        this.clearLayersArray("base");
+        this.clearLayersArray("data");
+        
+        var containsOverlays = false;
+        var containsBaseLayers = false;
+        
+        // Save state -- for checking layer if the map state changed.
+        // We save this before redrawing, because in the process of redrawing
+        // we will trigger more visibility changes, and we want to not redraw
+        // and enter an infinite loop.
+        var len = this.map.layers.length;
+        this.layerStates = new Array(len);
+        for (var i=0; i <len; i++) {
+            var layer = this.map.layers[i];
+            this.layerStates[i] = {
+                'name': layer.name, 
+                'visibility': layer.visibility,
+                'inRange': layer.inRange,
+                'id': layer.id
+            };
+        }    
+
+        var layers = this.map.layers.slice();
+        if (!this.ascending) { layers.reverse(); }
+        for(var i=0, len=layers.length; i<len; i++) {
+            var layer = layers[i];
+            var baseLayer = layer.isBaseLayer;
+
+            if (layer.displayInLayerSwitcher) {
+
+                if (baseLayer) {
+                    containsBaseLayers = true;
+                } else {
+                    containsOverlays = true;
+                }    
+
+                // only check a baselayer if it is *the* baselayer, check data
+                //  layers if they are visible
+                var checked = (baseLayer) ? (layer == this.map.baseLayer)
+                                          : layer.getVisibility();
+    
+                // create input element
+                var inputElem = document.createElement("input");
+                inputElem.id = this.id + "_input_" + layer.name;
+                inputElem.name = (baseLayer) ? "baseLayers" : layer.name;
+                inputElem.type = (baseLayer) ? "radio" : "checkbox";
+                inputElem.value = layer.name;
+                inputElem.checked = checked;
+                inputElem.defaultChecked = checked;
+
+                if (!baseLayer && !layer.inRange) {
+                    inputElem.disabled = true;
+                }
+                var context = {
+                    'inputElem': inputElem,
+                    'layer': layer,
+                    'layerSwitcher': this
+                };
+                OpenLayers.Event.observe(inputElem, "mouseup", 
+                    OpenLayers.Function.bindAsEventListener(this.onInputClick,
+                                                            context)
+                );
+                
+                // create span
+                var labelSpan = document.createElement("span");
+                if (!baseLayer && !layer.inRange) {
+                    labelSpan.style.color = "gray";
+                }
+                labelSpan.innerHTML = layer.name;
+                labelSpan.style.verticalAlign = (baseLayer) ? "bottom" 
+                                                            : "baseline";
+                OpenLayers.Event.observe(labelSpan, "click", 
+                    OpenLayers.Function.bindAsEventListener(this.onInputClick,
+                                                            context)
+                );
+                // create line break
+                var br = document.createElement("br");
+    
+                
+                var groupArray = (baseLayer) ? this.baseLayers
+                                             : this.dataLayers;
+                groupArray.push({
+                    'layer': layer,
+                    'inputElem': inputElem,
+                    'labelSpan': labelSpan
+                });
+                                                     
+    
+                var groupDiv = (baseLayer) ? this.baseLayersDiv
+                                           : this.dataLayersDiv;
+                groupDiv.appendChild(inputElem);
+                groupDiv.appendChild(labelSpan);
+                groupDiv.appendChild(br);
+            }
+        }
+
+        // if no overlays, dont display the overlay label
+        this.dataLbl.style.display = (containsOverlays) ? "" : "none";        
+        
+        // if no baselayers, dont display the baselayer label
+        this.baseLbl.style.display = (containsBaseLayers) ? "" : "none";        
+
+        return this.div;
+    },
+
+    /** 
+     * Method:
+     * A label has been clicked, check or uncheck its corresponding input
+     * 
+     * Parameters:
+     * e - {Event} 
+     *
+     * Context:  
+     *  - {DOMElement} inputElem
+     *  - {<OpenLayers.Control.LayerSwitcher>} layerSwitcher
+     *  - {<OpenLayers.Layer>} layer
+     */
+
+    onInputClick: function(e) {
+
+        if (!this.inputElem.disabled) {
+            if (this.inputElem.type == "radio") {
+                this.inputElem.checked = true;
+                this.layer.map.setBaseLayer(this.layer);
+            } else {
+                this.inputElem.checked = !this.inputElem.checked;
+                this.layerSwitcher.updateMap();
+            }
+        }
+        OpenLayers.Event.stop(e);
+    },
+    
+    /**
+     * Method: onLayerClick
+     * Need to update the map accordingly whenever user clicks in either of
+     *     the layers.
+     * 
+     * Parameters: 
+     * e - {Event} 
+     */
+    onLayerClick: function(e) {
+        this.updateMap();
+    },
+
+
+    /** 
+     * Method: updateMap
+     * Cycles through the loaded data and base layer input arrays and makes
+     *     the necessary calls to the Map object such that that the map's 
+     *     visual state corresponds to what the user has selected in 
+     *     the control.
+     */
+    updateMap: function() {
+
+        // set the newly selected base layer        
+        for(var i=0, len=this.baseLayers.length; i<len; i++) {
+            var layerEntry = this.baseLayers[i];
+            if (layerEntry.inputElem.checked) {
+                this.map.setBaseLayer(layerEntry.layer, false);
+            }
+        }
+
+        // set the correct visibilities for the overlays
+        for(var i=0, len=this.dataLayers.length; i<len; i++) {
+            var layerEntry = this.dataLayers[i];   
+            layerEntry.layer.setVisibility(layerEntry.inputElem.checked);
+        }
+
+    },
+
+    /** 
+     * Method: maximizeControl
+     * Set up the labels and divs for the control
+     * 
+     * Parameters:
+     * e - {Event} 
+     */
+    maximizeControl: function(e) {
+
+        //HACK HACK HACK - find a way to auto-size this layerswitcher
+        this.div.style.width = "20em";
+        this.div.style.height = "";
+
+        this.showControls(false);
+
+        if (e != null) {
+            OpenLayers.Event.stop(e);                                            
+        }
+    },
+    
+    /** 
+     * Method: minimizeControl
+     * Hide all the contents of the control, shrink the size, 
+     *     add the maximize icon
+     *
+     * Parameters:
+     * e - {Event} 
+     */
+    minimizeControl: function(e) {
+
+        this.div.style.width = "0px";
+        this.div.style.height = "0px";
+
+        this.showControls(true);
+
+        if (e != null) {
+            OpenLayers.Event.stop(e);                                            
+        }
+    },
+
+    /**
+     * Method: showControls
+     * Hide/Show all LayerSwitcher controls depending on whether we are
+     *     minimized or not
+     * 
+     * Parameters:
+     * minimize - {Boolean}
+     */
+    showControls: function(minimize) {
+
+        this.maximizeDiv.style.display = minimize ? "" : "none";
+        this.minimizeDiv.style.display = minimize ? "none" : "";
+
+        this.layersDiv.style.display = minimize ? "none" : "";
+    },
+    
+    /** 
+     * Method: loadContents
+     * Set up the labels and divs for the control
+     */
+    loadContents: function() {
+
+        //configure main div
+        this.div.style.position = "absolute";
+        this.div.style.top = "25px";
+        this.div.style.right = "0px";
+        this.div.style.left = "";
+        this.div.style.fontFamily = "sans-serif";
+        this.div.style.fontWeight = "bold";
+        this.div.style.marginTop = "3px";
+        this.div.style.marginLeft = "3px";
+        this.div.style.marginBottom = "3px";
+        this.div.style.fontSize = "smaller";   
+        this.div.style.color = "white";   
+        this.div.style.backgroundColor = "transparent";
+    
+        OpenLayers.Event.observe(this.div, "mouseup", 
+            OpenLayers.Function.bindAsEventListener(this.mouseUp, this));
+        OpenLayers.Event.observe(this.div, "click",
+                      this.ignoreEvent);
+        OpenLayers.Event.observe(this.div, "mousedown",
+            OpenLayers.Function.bindAsEventListener(this.mouseDown, this));
+        OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);
+
+
+        // layers list div        
+        this.layersDiv = document.createElement("div");
+        this.layersDiv.id = this.id + "_layersDiv";
+        this.layersDiv.style.paddingTop = "5px";
+        this.layersDiv.style.paddingLeft = "10px";
+        this.layersDiv.style.paddingBottom = "5px";
+        this.layersDiv.style.paddingRight = "75px";
+        this.layersDiv.style.backgroundColor = this.activeColor;        
+
+        // had to set width/height to get transparency in IE to work.
+        // thanks -- http://jszen.blogspot.com/2005/04/ie6-opacity-filter-caveat.html
+        //
+        this.layersDiv.style.width = "100%";
+        this.layersDiv.style.height = "100%";
+
+
+        this.baseLbl = document.createElement("div");
+        this.baseLbl.innerHTML = OpenLayers.i18n("baseLayer");
+        this.baseLbl.style.marginTop = "3px";
+        this.baseLbl.style.marginLeft = "3px";
+        this.baseLbl.style.marginBottom = "3px";
+        
+        this.baseLayersDiv = document.createElement("div");
+        this.baseLayersDiv.style.paddingLeft = "10px";
+        /*OpenLayers.Event.observe(this.baseLayersDiv, "click", 
+            OpenLayers.Function.bindAsEventListener(this.onLayerClick, this));
+        */
+                     
+
+        this.dataLbl = document.createElement("div");
+        this.dataLbl.innerHTML = OpenLayers.i18n("overlays");
+        this.dataLbl.style.marginTop = "3px";
+        this.dataLbl.style.marginLeft = "3px";
+        this.dataLbl.style.marginBottom = "3px";
+        
+        this.dataLayersDiv = document.createElement("div");
+        this.dataLayersDiv.style.paddingLeft = "10px";
+
+        if (this.ascending) {
+            this.layersDiv.appendChild(this.baseLbl);
+            this.layersDiv.appendChild(this.baseLayersDiv);
+            this.layersDiv.appendChild(this.dataLbl);
+            this.layersDiv.appendChild(this.dataLayersDiv);
+        } else {
+            this.layersDiv.appendChild(this.dataLbl);
+            this.layersDiv.appendChild(this.dataLayersDiv);
+            this.layersDiv.appendChild(this.baseLbl);
+            this.layersDiv.appendChild(this.baseLayersDiv);
+        }    
+ 
+        this.div.appendChild(this.layersDiv);
+
+        OpenLayers.Rico.Corner.round(this.div, {corners: "tl bl",
+                                        bgColor: "transparent",
+                                        color: this.activeColor,
+                                        blend: false});
+
+        OpenLayers.Rico.Corner.changeOpacity(this.layersDiv, 0.75);
+
+        var imgLocation = OpenLayers.Util.getImagesLocation();
+        var sz = new OpenLayers.Size(18,18);        
+
+        // maximize button div
+        var img = imgLocation + 'layer-switcher-maximize.png';
+        this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
+                                    "OpenLayers_Control_MaximizeDiv", 
+                                    null, 
+                                    sz, 
+                                    img, 
+                                    "absolute");
+        this.maximizeDiv.style.top = "5px";
+        this.maximizeDiv.style.right = "0px";
+        this.maximizeDiv.style.left = "";
+        this.maximizeDiv.style.display = "none";
+        OpenLayers.Event.observe(this.maximizeDiv, "click", 
+            OpenLayers.Function.bindAsEventListener(this.maximizeControl, this)
+        );
+        
+        this.div.appendChild(this.maximizeDiv);
+
+        // minimize button div
+        var img = imgLocation + 'layer-switcher-minimize.png';
+        var sz = new OpenLayers.Size(18,18);        
+        this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
+                                    "OpenLayers_Control_MinimizeDiv", 
+                                    null, 
+                                    sz, 
+                                    img, 
+                                    "absolute");
+        this.minimizeDiv.style.top = "5px";
+        this.minimizeDiv.style.right = "0px";
+        this.minimizeDiv.style.left = "";
+        this.minimizeDiv.style.display = "none";
+        OpenLayers.Event.observe(this.minimizeDiv, "click", 
+            OpenLayers.Function.bindAsEventListener(this.minimizeControl, this)
+        );
+
+        this.div.appendChild(this.minimizeDiv);
+    },
+    
+    /** 
+     * Method: ignoreEvent
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    ignoreEvent: function(evt) {
+        OpenLayers.Event.stop(evt);
+    },
+
+    /** 
+     * Method: mouseDown
+     * Register a local 'mouseDown' flag so that we'll know whether or not
+     *     to ignore a mouseUp event
+     * 
+     * Parameters:
+     * evt - {Event}
+     */
+    mouseDown: function(evt) {
+        this.isMouseDown = true;
+        this.ignoreEvent(evt);
+    },
+
+    /** 
+     * Method: mouseUp
+     * If the 'isMouseDown' flag has been set, that means that the drag was 
+     *     started from within the LayerSwitcher control, and thus we can 
+     *     ignore the mouseup. Otherwise, let the Event continue.
+     *  
+     * Parameters:
+     * evt - {Event} 
+     */
+    mouseUp: function(evt) {
+        if (this.isMouseDown) {
+            this.isMouseDown = false;
+            this.ignoreEvent(evt);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Control.LayerSwitcher"
+});
+/* ======================================================================
+    OpenLayers/Control/MouseDefaults.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.MouseDefaults
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.MouseDefaults = OpenLayers.Class(OpenLayers.Control, {
+
+    /** WARNING WARNING WARNING!!!
+        This class is DEPRECATED in 2.4 and will be removed by 3.0.
+        If you need this functionality, use Control.Navigation instead!!! */
+
+    /** 
+     * Property: performedDrag
+     * {Boolean}
+     */
+    performedDrag: false,
+
+    /** 
+     * Property: wheelObserver 
+     * {Function}
+     */
+    wheelObserver: null,
+
+    /** 
+     * Constructor: OpenLayers.Control.MouseDefaults
+     */
+    initialize: function() {
+        OpenLayers.Control.prototype.initialize.apply(this, arguments);
+    },
+
+    /**
+     * APIMethod: destroy
+     */    
+    destroy: function() {
+        
+        if (this.handler) {
+            this.handler.destroy();
+        }
+        this.handler = null;
+
+        this.map.events.un({
+            "click": this.defaultClick,
+            "dblclick": this.defaultDblClick,
+            "mousedown": this.defaultMouseDown,
+            "mouseup": this.defaultMouseUp,
+            "mousemove": this.defaultMouseMove,
+            "mouseout": this.defaultMouseOut,
+            scope: this
+        });
+
+        //unregister mousewheel events specifically on the window and document
+        OpenLayers.Event.stopObserving(window, "DOMMouseScroll", 
+                                        this.wheelObserver);
+        OpenLayers.Event.stopObserving(window, "mousewheel", 
+                                        this.wheelObserver);
+        OpenLayers.Event.stopObserving(document, "mousewheel", 
+                                        this.wheelObserver);
+        this.wheelObserver = null;
+                      
+        OpenLayers.Control.prototype.destroy.apply(this, arguments);        
+    },
+
+    /**
+     * Method: draw
+     */
+    draw: function() {
+        this.map.events.on({
+            "click": this.defaultClick,
+            "dblclick": this.defaultDblClick,
+            "mousedown": this.defaultMouseDown,
+            "mouseup": this.defaultMouseUp,
+            "mousemove": this.defaultMouseMove,
+            "mouseout": this.defaultMouseOut,
+            scope: this
+        });
+
+        this.registerWheelEvents();
+
+    },
+
+    /**
+     * Method: registerWheelEvents
+     */
+    registerWheelEvents: function() {
+
+        this.wheelObserver = OpenLayers.Function.bindAsEventListener(
+            this.onWheelEvent, this
+        );
+        
+        //register mousewheel events specifically on the window and document
+        OpenLayers.Event.observe(window, "DOMMouseScroll", this.wheelObserver);
+        OpenLayers.Event.observe(window, "mousewheel", this.wheelObserver);
+        OpenLayers.Event.observe(document, "mousewheel", this.wheelObserver);
+    },
+
+    /**
+     * Method: defaultClick
+     * 
+     * Parameters:
+     * evt - {Event} 
+     *
+     * Returns:
+     * {Boolean}
+     */
+    defaultClick: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        var notAfterDrag = !this.performedDrag;
+        this.performedDrag = false;
+        return notAfterDrag;
+    },
+
+    /**
+     * Method: defaultDblClick
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultDblClick: function (evt) {
+        var newCenter = this.map.getLonLatFromViewPortPx( evt.xy ); 
+        this.map.setCenter(newCenter, this.map.zoom + 1);
+        OpenLayers.Event.stop(evt);
+        return false;
+    },
+
+    /**
+     * Method: defaultMouseDown
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseDown: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        this.mouseDragStart = evt.xy.clone();
+        this.performedDrag  = false;
+        if (evt.shiftKey) {
+            this.map.div.style.cursor = "crosshair";
+            this.zoomBox = OpenLayers.Util.createDiv('zoomBox',
+                                                     this.mouseDragStart,
+                                                     null,
+                                                     null,
+                                                     "absolute",
+                                                     "2px solid red");
+            this.zoomBox.style.backgroundColor = "white";
+            this.zoomBox.style.filter = "alpha(opacity=50)"; // IE
+            this.zoomBox.style.opacity = "0.50";
+            this.zoomBox.style.fontSize = "1px";
+            this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+            this.map.viewPortDiv.appendChild(this.zoomBox);
+        }
+        document.onselectstart=function() { return false; };
+        OpenLayers.Event.stop(evt);
+    },
+
+    /**
+     * Method: defaultMouseMove
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseMove: function (evt) {
+        // record the mouse position, used in onWheelEvent
+        this.mousePosition = evt.xy.clone();
+
+        if (this.mouseDragStart != null) {
+            if (this.zoomBox) {
+                var deltaX = Math.abs(this.mouseDragStart.x - evt.xy.x);
+                var deltaY = Math.abs(this.mouseDragStart.y - evt.xy.y);
+                this.zoomBox.style.width = Math.max(1, deltaX) + "px";
+                this.zoomBox.style.height = Math.max(1, deltaY) + "px";
+                if (evt.xy.x < this.mouseDragStart.x) {
+                    this.zoomBox.style.left = evt.xy.x+"px";
+                }
+                if (evt.xy.y < this.mouseDragStart.y) {
+                    this.zoomBox.style.top = evt.xy.y+"px";
+                }
+            } else {
+                var deltaX = this.mouseDragStart.x - evt.xy.x;
+                var deltaY = this.mouseDragStart.y - evt.xy.y;
+                var size = this.map.getSize();
+                var newXY = new OpenLayers.Pixel(size.w / 2 + deltaX,
+                                                 size.h / 2 + deltaY);
+                var newCenter = this.map.getLonLatFromViewPortPx( newXY ); 
+                this.map.setCenter(newCenter, null, true);
+                this.mouseDragStart = evt.xy.clone();
+                this.map.div.style.cursor = "move";
+            }
+            this.performedDrag = true;
+        }
+    },
+
+    /**
+     * Method: defaultMouseUp
+     * 
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     */
+    defaultMouseUp: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        if (this.zoomBox) {
+            this.zoomBoxEnd(evt);    
+        } else {
+            if (this.performedDrag) {
+                this.map.setCenter(this.map.center);
+            }
+        }
+        document.onselectstart=null;
+        this.mouseDragStart = null;
+        this.map.div.style.cursor = "";
+    },
+
+    /**
+     * Method: defaultMouseOut
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseOut: function (evt) {
+        if (this.mouseDragStart != null && 
+            OpenLayers.Util.mouseLeft(evt, this.map.div)) {
+            if (this.zoomBox) {
+                this.removeZoomBox();
+            }
+            this.mouseDragStart = null;
+        }
+    },
+
+
+    /** 
+     * Method: defaultWheelUp
+     * User spun scroll wheel up
+     * 
+     */
+    defaultWheelUp: function(evt) {
+        if (this.map.getZoom() <= this.map.getNumZoomLevels()) {
+            this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),
+                               this.map.getZoom() + 1);
+        }
+    },
+
+    /**
+     * Method: defaultWheelDown
+     * User spun scroll wheel down
+     */
+    defaultWheelDown: function(evt) {
+        if (this.map.getZoom() > 0) {
+            this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),
+                               this.map.getZoom() - 1);
+        }
+    },
+
+    /**
+     * Method: zoomBoxEnd
+     * Zoombox function. 
+     */
+    zoomBoxEnd: function(evt) {
+        if (this.mouseDragStart != null) {
+            if (Math.abs(this.mouseDragStart.x - evt.xy.x) > 5 ||    
+                Math.abs(this.mouseDragStart.y - evt.xy.y) > 5) {   
+                var start = this.map.getLonLatFromViewPortPx( this.mouseDragStart ); 
+                var end = this.map.getLonLatFromViewPortPx( evt.xy );
+                var top = Math.max(start.lat, end.lat);
+                var bottom = Math.min(start.lat, end.lat);
+                var left = Math.min(start.lon, end.lon);
+                var right = Math.max(start.lon, end.lon);
+                var bounds = new OpenLayers.Bounds(left, bottom, right, top);
+                this.map.zoomToExtent(bounds);
+            } else {
+                var end = this.map.getLonLatFromViewPortPx( evt.xy );
+                this.map.setCenter(new OpenLayers.LonLat(
+                  (end.lon),
+                  (end.lat)
+                 ), this.map.getZoom() + 1);
+            }    
+            this.removeZoomBox();
+       }
+    },
+
+    /**
+     * Method: removeZoomBox
+     * Remove the zoombox from the screen and nullify our reference to it.
+     */
+    removeZoomBox: function() {
+        this.map.viewPortDiv.removeChild(this.zoomBox);
+        this.zoomBox = null;
+    },
+
+
+/**
+ *  Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/
+ */
+
+
+    /**
+     * Method: onWheelEvent
+     * Catch the wheel event and handle it xbrowserly
+     *
+     * Parameters: 
+     * e - {Event} 
+     */
+    onWheelEvent: function(e){
+    
+        // first determine whether or not the wheeling was inside the map
+        var inMap = false;
+        var elem = OpenLayers.Event.element(e);
+        while(elem != null) {
+            if (this.map && elem == this.map.div) {
+                inMap = true;
+                break;
+            }
+            elem = elem.parentNode;
+        }
+        
+        if (inMap) {
+            
+            var delta = 0;
+            if (!e) {
+                e = window.event;
+            }
+            if (e.wheelDelta) {
+                delta = e.wheelDelta/120; 
+                if (window.opera && window.opera.version() < 9.2) {
+                    delta = -delta;
+                }
+            } else if (e.detail) {
+                delta = -e.detail / 3;
+            }
+            if (delta) {
+                // add the mouse position to the event because mozilla has a bug
+                // with clientX and clientY (see https://bugzilla.mozilla.org/show_bug.cgi?id=352179)
+                // getLonLatFromViewPortPx(e) returns wrong values
+                e.xy = this.mousePosition;
+
+                if (delta < 0) {
+                   this.defaultWheelDown(e);
+                } else {
+                   this.defaultWheelUp(e);
+                }
+            }
+            
+            //only wheel the map, not the window
+            OpenLayers.Event.stop(e);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Control.MouseDefaults"
+});
+/* ======================================================================
     OpenLayers/Control/MousePosition.js
    ====================================================================== */
 
@@ -5630,7 +8726,7 @@
      * APIProperty: numDigits
      * {Integer}
      */
-    numdigits: 5,
+    numDigits: 5,
     
     /** 
      * APIProperty: granularity
@@ -5734,7 +8830,7 @@
      * lonLat - {<OpenLayers.LonLat>} Location to display
      */
     formatOutput: function(lonLat) {
-        var digits = parseInt(this.numdigits);
+        var digits = parseInt(this.numDigits);
         var newHtml =
             this.prefix +
             lonLat.lon.toFixed(digits) +
@@ -5755,6 +8851,408 @@
     CLASS_NAME: "OpenLayers.Control.MousePosition"
 });
 /* ======================================================================
+    OpenLayers/Control/NavigationHistory.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.NavigationHistory
+ * A navigation history control.  This is a meta-control, that creates two
+ *     dependent controls: <previous> and <next>.  Call the trigger method
+ *     on the <previous> and <next> controls to restore previous and next
+ *     history states.  The previous and next controls will become active
+ *     when there are available states to restore and will become deactive
+ *     when there are no states to restore.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.NavigationHistory = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * Property: type
+     * {String} Note that this control is not intended to be added directly
+     *     to a control panel.  Instead, add the sub-controls previous and
+     *     next.  These sub-controls are button type controls that activate
+     *     and deactivate themselves.  If this parent control is added to
+     *     a panel, it will act as a toggle.
+     */
+    type: OpenLayers.Control.TYPE_TOGGLE,
+
+    /**
+     * APIProperty: previous
+     * {<OpenLayers.Control>} A button type control whose trigger method restores
+     *     the previous state managed by this control.
+     */
+    previous: null,
+    
+    /**
+     * APIProperty: previousOptions
+     * {Object} Set this property on the options argument of the constructor
+     *     to set optional properties on the <previous> control.
+     */
+    previousOptions: null,
+    
+    /**
+     * APIProperty: next
+     * {<OpenLayers.Control>} A button type control whose trigger method restores
+     *     the next state managed by this control.
+     */
+    next: null,
+
+    /**
+     * APIProperty: nextOptions
+     * {Object} Set this property on the options argument of the constructor
+     *     to set optional properties on the <next> control.
+     */
+    nextOptions: null,
+
+    /**
+     * APIProperty: limit
+     * {Integer} Optional limit on the number of history items to retain.  If
+     *     null, there is no limit.  Default is 50.
+     */
+    limit: 50,
+
+    /**
+     * Property: activateOnDraw
+     * {Boolean} Activate the control when it is first added to the map.
+     *     Default is true.
+     */
+    activateOnDraw: true,
+
+    /**
+     * Property: clearOnDeactivate
+     * {Boolean} Clear the history when the control is deactivated.  Default
+     *     is false.
+     */
+    clearOnDeactivate: false,
+
+    /**
+     * Property: registry
+     * {Object} An object with keys corresponding to event types.  Values
+     *     are functions that return an object representing the current state.
+     */
+    registry: null,
+
+    /**
+     * Property: nextStack
+     * {Array} Array of items in the history.
+     */
+    nextStack: null,
+
+    /**
+     * Property: previousStack
+     * {Array} List of items in the history.  First item represents the current
+     *     state.
+     */
+    previousStack: null,
+    
+    /**
+     * Property: listeners
+     * {Object} An object containing properties corresponding to event types.
+     *     This object is used to configure the control and is modified on
+     *     construction.
+     */
+    listeners: null,
+    
+    /**
+     * Property: restoring
+     * {Boolean} Currently restoring a history state.  This is set to true
+     *     before calling restore and set to false after restore returns.
+     */
+    restoring: false,
+    
+    /**
+     * Constructor: OpenLayers.Control.NavigationHistory 
+     * 
+     * Parameters:
+     * options - {Object} An optional object whose properties will be used
+     *     to extend the control.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        
+        this.registry = OpenLayers.Util.extend({
+            "moveend": function() {
+                return {
+                    center: this.map.getCenter(),
+                    resolution: this.map.getResolution()                
+                };
+            }
+        }, this.registry);
+        
+        this.clear();
+
+        var previousOptions = {
+            trigger: OpenLayers.Function.bind(this.previousTrigger, this),
+            displayClass: this.displayClass + " " + this.displayClass + "Previous"
+        };
+        OpenLayers.Util.extend(previousOptions, this.previousOptions);
+        this.previous = new OpenLayers.Control.Button(previousOptions);
+        
+        var nextOptions = {
+            trigger: OpenLayers.Function.bind(this.nextTrigger, this),
+            displayClass: this.displayClass + " " + this.displayClass + "Next"
+        };
+        OpenLayers.Util.extend(nextOptions, this.nextOptions);
+        this.next = new OpenLayers.Control.Button(nextOptions);
+
+    },
+    
+    /**
+     * Method: onPreviousChange
+     * Called when the previous history stack changes.
+     *
+     * Parameters:
+     * state - {Object} An object representing the state to be restored
+     *     if previous is triggered again or null if no previous states remain.
+     * length - {Integer} The number of remaining previous states that can
+     *     be restored.
+     */
+    onPreviousChange: function(state, length) {
+        if(state && !this.previous.active) {
+            this.previous.activate();
+        } else if(!state && this.previous.active) {
+            this.previous.deactivate();
+        }
+    },
+    
+    /**
+     * Method: onNextChange
+     * Called when the next history stack changes.
+     *
+     * Parameters:
+     * state - {Object} An object representing the state to be restored
+     *     if next is triggered again or null if no next states remain.
+     * length - {Integer} The number of remaining next states that can
+     *     be restored.
+     */
+    onNextChange: function(state, length) {
+        if(state && !this.next.active) {
+            this.next.activate();
+        } else if(!state && this.next.active) {
+            this.next.deactivate();
+        }
+    },
+    
+    /**
+     * APIMethod: destroy
+     * Destroy the control.
+     */
+    destroy: function() {
+        OpenLayers.Control.prototype.destroy.apply(this);
+        this.previous.destroy();
+        this.next.destroy();
+        this.deactivate();
+        for(var prop in this) {
+            this[prop] = null;
+        }
+    },
+    
+    /** 
+     * Method: setMap
+     * Set the map property for the control and <previous> and <next> child
+     *     controls.
+     *
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        this.map = map;
+        this.next.setMap(map);
+        this.previous.setMap(map);
+    },
+
+    /**
+     * Method: draw
+     * Called when the control is added to the map.
+     */
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        this.next.draw();
+        this.previous.draw();
+        if(this.activateOnDraw) {
+            this.activate();
+        }
+    },
+    
+    /**
+     * Method: previousTrigger
+     * Restore the previous state.  If no items are in the previous history
+     *     stack, this has no effect.
+     *
+     * Returns:
+     * {Object} Item representing state that was restored.  Undefined if no
+     *     items are in the previous history stack.
+     */
+    previousTrigger: function() {
+        var current = this.previousStack.shift();
+        var state = this.previousStack.shift();
+        if(state != undefined) {
+            this.nextStack.unshift(current);
+            this.previousStack.unshift(state);
+            this.restoring = true;
+            this.restore(state);
+            this.restoring = false;
+            this.onNextChange(this.nextStack[0], this.nextStack.length);
+            this.onPreviousChange(
+                this.previousStack[1], this.previousStack.length - 1
+            );
+        } else {
+            this.previousStack.unshift(current);
+        }
+        return state;
+    },
+    
+    /**
+     * APIMethod: nextTrigger
+     * Restore the next state.  If no items are in the next history
+     *     stack, this has no effect.  The next history stack is populated
+     *     as states are restored from the previous history stack.
+     *
+     * Returns:
+     * {Object} Item representing state that was restored.  Undefined if no
+     *     items are in the next history stack.
+     */
+    nextTrigger: function() {
+        var state = this.nextStack.shift();
+        if(state != undefined) {
+            this.previousStack.unshift(state);
+            this.restoring = true;
+            this.restore(state);
+            this.restoring = false;
+            this.onNextChange(this.nextStack[0], this.nextStack.length);
+            this.onPreviousChange(
+                this.previousStack[1], this.previousStack.length - 1
+            );
+        }
+        return state;
+    },
+    
+    /**
+     * APIMethod: clear
+     * Clear history.
+     */
+    clear: function() {
+        this.previousStack = [];
+        this.nextStack = [];
+    },
+
+    /**
+     * Method: restore
+     * Update the state with the given object.
+     *
+     * Parameters:
+     * state - {Object} An object representing the state to restore.
+     */
+    restore: function(state) {
+        var zoom = this.map.getZoomForResolution(state.resolution);
+        this.map.setCenter(state.center, zoom);
+    },
+    
+    /**
+     * Method: setListeners
+     * Sets functions to be registered in the listeners object.
+     */
+    setListeners: function() {
+        this.listeners = {};
+        for(var type in this.registry) {
+            this.listeners[type] = OpenLayers.Function.bind(function() {
+                if(!this.restoring) {
+                    var state = this.registry[type].apply(this, arguments);
+                    this.previousStack.unshift(state);
+                    if(this.previousStack.length > 1) {
+                        this.onPreviousChange(
+                            this.previousStack[1], this.previousStack.length - 1
+                        );
+                    }
+                    if(this.previousStack.length > (this.limit + 1)) {
+                        this.previousStack.pop();
+                    }
+                    if(this.nextStack.length > 0) {
+                        this.nextStack = [];
+                        this.onNextChange(null, 0);
+                    }
+                }
+                return true;
+            }, this);
+        }
+    },
+
+    /**
+     * APIMethod: activate
+     * Activate the control.  This registers any listeners.
+     *
+     * Returns:
+     * {Boolean} Control successfully activated.
+     */
+    activate: function() {
+        var activated = false;
+        if(this.map) {
+            if(OpenLayers.Control.prototype.activate.apply(this)) {
+                if(this.listeners == null) {
+                    this.setListeners();
+                }
+                for(var type in this.listeners) {
+                    this.map.events.register(type, this, this.listeners[type]);
+                }
+                activated = true;
+                if(this.previousStack.length == 0) {
+                    this.initStack();
+                }
+            }
+        }
+        return activated;
+    },
+    
+    /**
+     * Method: initStack
+     * Called after the control is activated if the previous history stack is
+     *     empty.
+     */
+    initStack: function() {
+        if(this.map.getCenter()) {
+            this.listeners.moveend();
+        }
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * Deactivate the control.  This unregisters any listeners.
+     *
+     * Returns:
+     * {Boolean} Control successfully deactivated.
+     */
+    deactivate: function() {
+        var deactivated = false;
+        if(this.map) {
+            if(OpenLayers.Control.prototype.deactivate.apply(this)) {
+                for(var type in this.listeners) {
+                    this.map.events.unregister(
+                        type, this, this.listeners[type]
+                    );
+                }
+                if(this.clearOnDeactivate) {
+                    this.clear();
+                }
+                deactivated = true;
+            }
+        }
+        return deactivated;
+    },
+    
+    CLASS_NAME: "OpenLayers.Control.NavigationHistory"
+});
+
+/* ======================================================================
     OpenLayers/Control/PanZoom.js
    ====================================================================== */
 
@@ -5871,7 +9369,7 @@
     _addButton:function(id, img, xy, sz) {
         var imgLocation = OpenLayers.Util.getImagesLocation() + img;
         var btn = OpenLayers.Util.createAlphaImageDiv(
-                                    "OpenLayers_Control_PanZoom_" + id, 
+                                    this.id + "_" + id, 
                                     xy, sz, imgLocation, "absolute");
 
         //we want to add the outer div
@@ -5959,6 +9457,373 @@
  */
 OpenLayers.Control.PanZoom.Y = 4;
 /* ======================================================================
+    OpenLayers/Control/Panel.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Panel
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, {
+    /**
+     * Property: controls
+     * {Array(<OpenLayers.Control>)}
+     */
+    controls: null,    
+    
+    /** 
+     * APIProperty: defaultControl
+     * <OpenLayers.Control> The control which is activated when the control is
+     * activated (turned on), which also happens at instantiation.
+     */
+    defaultControl: null, 
+
+    /**
+     * Constructor: OpenLayers.Control.Panel
+     * Create a new control panel.
+     * 
+     * Parameters:
+     * options - {Object} An optional object whose properties will be used
+     *     to extend the control.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.controls = [];
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        OpenLayers.Control.prototype.destroy.apply(this, arguments);
+        for(var i = this.controls.length - 1 ; i >= 0; i--) {
+            if(this.controls[i].events) {
+                this.controls[i].events.un({
+                    "activate": this.redraw,
+                    "deactivate": this.redraw,
+                    scope: this
+                });
+            }
+            OpenLayers.Event.stopObservingElement(this.controls[i].panel_div);
+            this.controls[i].panel_div = null;
+        }    
+    },
+
+    /**
+     * APIMethod: activate
+     */
+    activate: function() {
+        if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+            for(var i=0, len=this.controls.length; i<len; i++) {
+                if (this.controls[i] == this.defaultControl) {
+                    this.controls[i].activate();
+                }
+            }    
+            this.redraw();
+            return true;
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * APIMethod: deactivate
+     */
+    deactivate: function() {
+        if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+            for(var i=0, len=this.controls.length; i<len; i++) {
+                this.controls[i].deactivate();
+            }    
+            return true;
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: draw
+     *
+     * Returns:
+     * {DOMElement}
+     */    
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        for (var i=0, len=this.controls.length; i<len; i++) {
+            this.map.addControl(this.controls[i]);
+            this.controls[i].deactivate();
+            this.controls[i].events.on({
+                "activate": this.redraw,
+                "deactivate": this.redraw,
+                scope: this
+            });
+        }
+        this.activate();
+        return this.div;
+    },
+
+    /**
+     * Method: redraw
+     */
+    redraw: function() {
+        this.div.innerHTML = "";
+        if (this.active) {
+            for (var i=0, len=this.controls.length; i<len; i++) {
+                var element = this.controls[i].panel_div;
+                if (this.controls[i].active) {
+                    element.className = this.controls[i].displayClass + "ItemActive";
+                } else {    
+                    element.className = this.controls[i].displayClass + "ItemInactive";
+                }    
+                this.div.appendChild(element);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: activateControl
+     * 
+     * Parameters:
+     * control - {<OpenLayers.Control>}
+     */
+    activateControl: function (control) {
+        if (!this.active) { return false; }
+        if (control.type == OpenLayers.Control.TYPE_BUTTON) {
+            control.trigger();
+            this.redraw();
+            return;
+        }
+        if (control.type == OpenLayers.Control.TYPE_TOGGLE) {
+            if (control.active) {
+                control.deactivate();
+            } else {
+                control.activate();
+            }
+            this.redraw();
+            return;
+        }
+        for (var i=0, len=this.controls.length; i<len; i++) {
+            if (this.controls[i] != control) {
+                if (this.controls[i].type != OpenLayers.Control.TYPE_TOGGLE) {
+                    this.controls[i].deactivate();
+                }
+            }
+        }
+        control.activate();
+    },
+
+    /**
+     * APIMethod: addControls
+     * To build a toolbar, you add a set of controls to it. addControls
+     * lets you add a single control or a list of controls to the 
+     * Control Panel.
+     *
+     * Parameters:
+     * controls - {<OpenLayers.Control>} 
+     */    
+    addControls: function(controls) {
+        if (!(controls instanceof Array)) {
+            controls = [controls];
+        }
+        this.controls = this.controls.concat(controls);
+        
+        // Give each control a panel_div which will be used later.
+        // Access to this div is via the panel_div attribute of the 
+        // control added to the panel.
+        // Also, stop mousedowns and clicks, but don't stop mouseup,
+        // since they need to pass through.
+        for (var i=0, len=controls.length; i<len; i++) {
+            var element = document.createElement("div");
+            var textNode = document.createTextNode(" ");
+            controls[i].panel_div = element;
+            if (controls[i].title != "") {
+                controls[i].panel_div.title = controls[i].title;
+            }
+            OpenLayers.Event.observe(controls[i].panel_div, "click", 
+                OpenLayers.Function.bind(this.onClick, this, controls[i]));
+            OpenLayers.Event.observe(controls[i].panel_div, "mousedown", 
+                OpenLayers.Function.bindAsEventListener(OpenLayers.Event.stop));
+        }    
+
+        if (this.map) { // map.addControl() has already been called on the panel
+            for (var i=0, len=controls.length; i<len; i++) {
+                this.map.addControl(controls[i]);
+                controls[i].deactivate();
+                controls[i].events.on({
+                    "activate": this.redraw,
+                    "deactivate": this.redraw,
+                    scope: this
+                });
+            }
+            this.redraw();
+        }
+    },
+   
+    /**
+     * Method: onClick
+     */
+    onClick: function (ctrl, evt) {
+        OpenLayers.Event.stop(evt ? evt : window.event);
+        this.activateControl(ctrl);
+    },
+
+    /**
+     * APIMethod: getControlsBy
+     * Get a list of controls with properties matching the given criteria.
+     *
+     * Parameter:
+     * 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(control[property]) evaluates to true, the control will be
+     *     included in the array returned.  If no controls 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) {
+        var test = (typeof match.test == "function");
+        var found = OpenLayers.Array.filter(this.controls, function(item) {
+            return item[property] == match || (test && match.test(item[property]));
+        });
+        return found;
+    },
+
+    /**
+     * APIMethod: getControlsByName
+     * Get a list of contorls with names matching the given name.
+     *
+     * Parameter:
+     * match - {String | Object} A control 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(control.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 name.
+     *     An empty array is returned if no matches are found.
+     */
+    getControlsByName: function(match) {
+        return this.getControlsBy("name", match);
+    },
+
+    /**
+     * APIMethod: getControlsByClass
+     * Get a list of controls of a given type (CLASS_NAME).
+     *
+     * Parameter:
+     * match - {String | Object} A control class name.  The type 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 type.
+     *     An empty array is returned if no matches are found.
+     */
+    getControlsByClass: function(match) {
+        return this.getControlsBy("CLASS_NAME", match);
+    },
+
+    CLASS_NAME: "OpenLayers.Control.Panel"
+});
+
+/* ======================================================================
+    OpenLayers/Control/Scale.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Scale
+ * Display a small scale indicator on the map.
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.Scale = OpenLayers.Class(OpenLayers.Control, {
+    
+    /**
+     * Parameter: element
+     * {DOMElement}
+     */
+    element: null,
+    
+    /**
+     * Constructor: OpenLayers.Control.Scale
+     * 
+     * Parameters:
+     * element - {DOMElement} 
+     * options - {Object} 
+     */
+    initialize: function(element, options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.element = OpenLayers.Util.getElement(element);        
+    },
+
+    /**
+     * Method: draw
+     * 
+     * Returns:
+     * {DOMElement}
+     */    
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        if (!this.element) {
+            this.element = document.createElement("div");
+            this.div.appendChild(this.element);
+        }
+        this.map.events.register( 'moveend', this, this.updateScale);
+        this.updateScale();
+        return this.div;
+    },
+   
+    /**
+     * Method: updateScale
+     */
+    updateScale: function() {
+        var scale = this.map.getScale();
+        if (!scale) {
+            return;
+        }
+
+        if (scale >= 9500 && scale <= 950000) {
+            scale = Math.round(scale / 1000) + "K";
+        } else if (scale >= 950000) {
+            scale = Math.round(scale / 1000000) + "M";
+        } else {
+            scale = Math.round(scale);
+        }    
+        
+        this.element.innerHTML = OpenLayers.i18n("scale", {'scaleDenom':scale});
+    }, 
+
+    CLASS_NAME: "OpenLayers.Control.Scale"
+});
+
+/* ======================================================================
     OpenLayers/Control/ScaleLine.js
    ====================================================================== */
 
@@ -6117,7 +9982,7 @@
             return;
         }
 
-        var curMapUnits = this.map.units;
+        var curMapUnits = this.map.getUnits();
         var inches = OpenLayers.INCHES_PER_UNIT;
 
         // convert maxWidth to map units
@@ -6163,6 +10028,45 @@
 });
 
 /* ======================================================================
+    OpenLayers/Control/ZoomToMaxExtent.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomToMaxExtent 
+ * Imlements a very simple button control. Designed to be used with a 
+ * <OpenLayers.Control.Panel>.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomToMaxExtent = OpenLayers.Class(OpenLayers.Control, {
+    /**
+     * Property: type
+     * TYPE_BUTTON.
+     */
+    type: OpenLayers.Control.TYPE_BUTTON,
+    
+    /*
+     * Method: trigger
+     * Do the zoom.
+     */
+    trigger: function() {
+        if (this.map) {
+            this.map.zoomToMaxExtent();
+        }    
+    },
+
+    CLASS_NAME: "OpenLayers.Control.ZoomToMaxExtent"
+});
+/* ======================================================================
     OpenLayers/Events.js
    ====================================================================== */
 
@@ -6273,6 +10177,21 @@
     },
 
     /**
+     * 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. 
      *
@@ -6520,7 +10439,7 @@
     BROWSER_EVENTS: [
         "mouseover", "mouseout",
         "mousedown", "mouseup", "mousemove", 
-        "click", "dblclick",
+        "click", "dblclick", "rightclick", "dblrightclick",
         "resize", "focus", "blur"
     ],
 
@@ -6560,6 +10479,34 @@
      */
     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,      
+
     /**
      * Constructor: OpenLayers.Events
      * Construct an OpenLayers.Events object.
@@ -6570,11 +10517,12 @@
      * eventTypes - {Array(String)} Array of custom application events 
      * 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) {
+    initialize: function (object, element, eventTypes, fallThrough, options) {
+        OpenLayers.Util.extend(this, options);
         this.object     = object;
         this.element    = element;
-        this.eventTypes = eventTypes;
         this.fallThrough = fallThrough;
         this.listeners  = {};
 
@@ -6586,9 +10534,10 @@
 
         // if eventTypes is specified, create a listeners list for each 
         // custom application event.
-        if (this.eventTypes != null) {
-            for (var i = 0; i < this.eventTypes.length; i++) {
-                this.addEventType(this.eventTypes[i]);
+        this.eventTypes = [];
+        if (eventTypes != null) {
+            for (var i=0, len=eventTypes.length; i<len; i++) {
+                this.addEventType(eventTypes[i]);
             }
         }
         
@@ -6625,6 +10574,7 @@
      */
     addEventType: function(eventName) {
         if (!this.listeners[eventName]) {
+            this.eventTypes.push(eventName);
             this.listeners[eventName] = [];
         }
     },
@@ -6636,7 +10586,7 @@
      * element - {HTMLDOMElement} a DOM element to attach browser events to
      */
     attachToElement: function (element) {
-        for (var i = 0; i < this.BROWSER_EVENTS.length; i++) {
+        for (var i=0, len=this.BROWSER_EVENTS.length; i<len; i++) {
             var eventType = this.BROWSER_EVENTS[i];
 
             // every browser event has a corresponding application event 
@@ -6700,16 +10650,14 @@
      */
     register: function (type, obj, func) {
 
-        if (func != null && 
-            ((this.eventTypes && OpenLayers.Util.indexOf(this.eventTypes, type) != -1) ||
-            OpenLayers.Util.indexOf(this.BROWSER_EVENTS, type) != -1)) {
+        if ( (func != null) && 
+             (OpenLayers.Util.indexOf(this.eventTypes, type) != -1) ) {
+
             if (obj == null)  {
                 obj = this.object;
             }
             var listeners = this.listeners[type];
-            if (listeners != null) {
-                listeners.push( {obj: obj, func: func} );
-            }
+            listeners.push( {obj: obj, func: func} );
         }
     },
 
@@ -6778,7 +10726,7 @@
         }
         var listeners = this.listeners[type];
         if (listeners != null) {
-            for (var i = 0; i < listeners.length; i++) {
+            for (var i=0, len=listeners.length; i<len; i++) {
                 if (listeners[i].obj == obj && listeners[i].func == func) {
                     listeners.splice(i, 1);
                     break;
@@ -6832,7 +10780,7 @@
                             this.listeners[type].slice() : null;
         if ((listeners != null) && (listeners.length > 0)) {
             var continueChain;
-            for (var i = 0; i < listeners.length; i++) {
+            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]);
@@ -6860,11 +10808,25 @@
      * evt - {Event} 
      */
     handleBrowserEvent: function (evt) {
-        evt.xy = this.getMousePosition(evt); 
+        if (this.includeXY) {
+            evt.xy = this.getMousePosition(evt);
+        } 
         this.triggerEvent(evt.type, evt);
     },
 
     /**
+     * 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:
@@ -6875,26 +10837,137 @@
      *                      for offsets
      */
     getMousePosition: function (evt) {
-        if (!this.element.offsets) {
-            this.element.offsets = OpenLayers.Util.pagePosition(this.element);
-            this.element.offsets[0] += (document.documentElement.scrollLeft
+        if (!this.includeXY) {
+            this.clearMouseCache();
+        } else if (!this.element.hasScrollEvent) {
+            OpenLayers.Event.observe(window, 'scroll', 
+                OpenLayers.Function.bind(this.clearMouseCache, this)); 
+            this.element.hasScrollEvent = true;
+        }
+        
+        if (!this.element.scrolls) {
+            this.element.scrolls = [];
+            this.element.scrolls[0] = (document.documentElement.scrollLeft
                          || document.body.scrollLeft);
-            this.element.offsets[1] += (document.documentElement.scrollTop
+            this.element.scrolls[1] = (document.documentElement.scrollTop
                          || document.body.scrollTop);
         }
+
+        if (!this.element.lefttop) {
+            this.element.lefttop = [];
+            this.element.lefttop[0] = (document.documentElement.clientLeft || 0);
+            this.element.lefttop[1] = (document.documentElement.clientTop || 0);
+        }
+        
+        if (!this.element.offsets) {
+            this.element.offsets = OpenLayers.Util.pagePosition(this.element);
+            this.element.offsets[0] += this.element.scrolls[0];
+            this.element.offsets[1] += this.element.scrolls[1];
+        }
         return new OpenLayers.Pixel(
-            (evt.clientX + (document.documentElement.scrollLeft
-                         || document.body.scrollLeft)) - this.element.offsets[0]
-                         - (document.documentElement.clientLeft || 0), 
-            (evt.clientY + (document.documentElement.scrollTop
-                         || document.body.scrollTop)) - this.element.offsets[1]
-                         - (document.documentElement.clientTop || 0)
+            (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]
         ); 
     },
 
     CLASS_NAME: "OpenLayers.Events"
 });
 /* ======================================================================
+    OpenLayers/Format.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Format
+ * Base class for format reading/writing a variety of formats.  Subclasses
+ *     of OpenLayers.Format are expected to have read and write methods.
+ */
+OpenLayers.Format = OpenLayers.Class({
+    
+    /**
+     * APIProperty: externalProjection
+     * {<OpenLayers.Projection>} When passed a externalProjection and
+     *     internalProjection, the format will reproject the geometries it
+     *     reads or writes. The externalProjection is the projection used by
+     *     the content which is passed into read or which comes out of write.
+     *     In order to reproject, a projection transformation function for the
+     *     specified projections must be available. This support may be 
+     *     provided via proj4js or via a custom transformation function. See
+     *     {<OpenLayers.Projection.addTransform>} for more information on
+     *     custom transformations.
+     */
+    externalProjection: null,
+
+    /**
+     * APIProperty: internalProjection
+     * {<OpenLayers.Projection>} When passed a externalProjection and
+     *     internalProjection, the format will reproject the geometries it
+     *     reads or writes. The internalProjection is the projection used by
+     *     the geometries which are returned by read or which are passed into
+     *     write.  In order to reproject, a projection transformation function
+     *     for the specified projections must be available. This support may be
+     *     provided via proj4js or via a custom transformation function. See
+     *     {<OpenLayers.Projection.addTransform>} for more information on
+     *     custom transformations.
+     */
+    internalProjection: null,
+
+    /**
+     * Constructor: OpenLayers.Format
+     * Instances of this class are not useful.  See one of the subclasses.
+     *
+     * Parameters:
+     * options - {Object} An optional object with properties to set on the
+     *           format
+     *
+     * Returns:
+     * An instance of OpenLayers.Format
+     */
+    initialize: function(options) {
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /**
+     * Method: read
+     * Read data from a string, and return an object whose type depends on the
+     * subclass. 
+     * 
+     * Parameters:
+     * data - {string} Data to read/parse.
+     *
+     * Returns:
+     * Depends on the subclass
+     */
+    read: function(data) {
+        OpenLayers.Console.userError(OpenLayers.i18n("readNotImplemented"));
+    },
+    
+    /**
+     * Method: write
+     * Accept an object, and return a string. 
+     *
+     * Parameters:
+     * object - {Object} Object to be serialized
+     *
+     * Returns:
+     * {String} A string representation of the object.
+     */
+    write: function(object) {
+        OpenLayers.Console.userError(OpenLayers.i18n("writeNotImplemented"));
+    },
+
+    CLASS_NAME: "OpenLayers.Format"
+});     
+/* ======================================================================
     OpenLayers/Lang/en.js
    ====================================================================== */
 
@@ -6975,7 +11048,7 @@
         "To get rid of this message, select a new BaseLayer " +
         "in the layer switcher in the upper-right corner.<br><br>" +
         "Most likely, this is because the ${layerLib} library " +
-        "script was either not correctly included.<br><br>" +
+        "script was not correctly included.<br><br>" +
         "Developers: For help getting this working correctly, " +
         "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
         "target='_blank'>click here</a>",
@@ -7019,6 +11092,193 @@
     'end': ''
 };
 /* ======================================================================
+    OpenLayers/Popup/Anchored.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Popup.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.Anchored
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Popup>
+ */
+OpenLayers.Popup.Anchored = 
+  OpenLayers.Class(OpenLayers.Popup, {
+
+    /** 
+     * Parameter: relativePosition
+     * {String} Relative position of the popup ("br", "tr", "tl" or "bl").
+     */
+    relativePosition: null,
+
+    /**
+     * Parameter: anchor
+     * {Object} Object to which we'll anchor the popup. Must expose a 
+     *     'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>).
+     */
+    anchor: null,
+
+    /** 
+    * Constructor: OpenLayers.Popup.Anchored
+    * 
+    * Parameters:
+    * id - {String}
+    * lonlat - {<OpenLayers.LonLat>}
+    * contentSize - {<OpenLayers.Size>}
+    * contentHTML - {String}
+    * anchor - {Object} Object which must expose a 'size' <OpenLayers.Size> 
+    *     and 'offset' <OpenLayers.Pixel> (generally an <OpenLayers.Icon>).
+    * closeBox - {Boolean}
+    * closeBoxCallback - {Function} Function to be called on closeBox click.
+    */
+    initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+                        closeBoxCallback) {
+        var newArguments = [
+            id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback
+        ];
+        OpenLayers.Popup.prototype.initialize.apply(this, newArguments);
+
+        this.anchor = (anchor != null) ? anchor 
+                                       : { size: new OpenLayers.Size(0,0),
+                                           offset: new OpenLayers.Pixel(0,0)};
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        this.anchor = null;
+        this.relativePosition = null;
+        
+        OpenLayers.Popup.prototype.destroy.apply(this, arguments);        
+    },
+
+    /**
+     * APIMethod: show
+     * Overridden from Popup since user might hide popup and then show() it 
+     *     in a new location (meaning we might want to update the relative
+     *     position on the show)
+     */
+    show: function() {
+        this.updatePosition();
+        OpenLayers.Popup.prototype.show.apply(this, arguments);
+    },
+
+    /**
+     * Method: moveTo
+     * Since the popup is moving to a new px, it might need also to be moved
+     *     relative to where the marker is. We first calculate the new 
+     *     relativePosition, and then we calculate the new px where we will 
+     *     put the popup, based on the new relative position. 
+     * 
+     *     If the relativePosition has changed, we must also call 
+     *     updateRelativePosition() to make any visual changes to the popup 
+     *     which are associated with putting it in a new relativePosition.
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     */
+    moveTo: function(px) {
+        var oldRelativePosition = this.relativePosition;
+        this.relativePosition = this.calculateRelativePosition(px);
+        
+        var newPx = this.calculateNewPx(px);
+        
+        var newArguments = new Array(newPx);        
+        OpenLayers.Popup.prototype.moveTo.apply(this, newArguments);
+        
+        //if this move has caused the popup to change its relative position, 
+        // we need to make the appropriate cosmetic changes.
+        if (this.relativePosition != oldRelativePosition) {
+            this.updateRelativePosition();
+        }
+    },
+
+    /**
+     * APIMethod: setSize
+     * 
+     * Parameters:
+     * contentSize - {<OpenLayers.Size>} the new size for the popup's 
+     *     contents div (in pixels).
+     */
+    setSize:function(contentSize) { 
+        OpenLayers.Popup.prototype.setSize.apply(this, arguments);
+
+        if ((this.lonlat) && (this.map)) {
+            var px = this.map.getLayerPxFromLonLat(this.lonlat);
+            this.moveTo(px);
+        }
+    },  
+    
+    /** 
+     * Method: calculateRelativePosition
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {String} The relative position ("br" "tr" "tl" "bl") at which the popup
+     *     should be placed.
+     */
+    calculateRelativePosition:function(px) {
+        var lonlat = this.map.getLonLatFromLayerPx(px);        
+        
+        var extent = this.map.getExtent();
+        var quadrant = extent.determineQuadrant(lonlat);
+        
+        return OpenLayers.Bounds.oppositeQuadrant(quadrant);
+    }, 
+
+    /**
+     * Method: updateRelativePosition
+     * The popup has been moved to a new relative location, so we may want to 
+     *     make some cosmetic adjustments to it. 
+     * 
+     *     Note that in the classic Anchored popup, there is nothing to do 
+     *     here, since the popup looks exactly the same in all four positions.
+     *     Subclasses such as the AnchoredBubble and Framed, however, will 
+     *     want to do something special here.
+     */
+    updateRelativePosition: function() {
+        //to be overridden by subclasses
+    },
+
+    /** 
+     * Method: calculateNewPx
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
+     *     relative to the passed-in px.
+     */
+    calculateNewPx:function(px) {
+        var newPx = px.offset(this.anchor.offset);
+        
+        //use contentSize if size is not already set
+        var size = this.size || this.contentSize;
+
+        var top = (this.relativePosition.charAt(0) == 't');
+        newPx.y += (top) ? -size.h : this.anchor.size.h;
+        
+        var left = (this.relativePosition.charAt(1) == 'l');
+        newPx.x += (left) ? -size.w : this.anchor.size.w;
+
+        return newPx;   
+    },
+
+    CLASS_NAME: "OpenLayers.Popup.Anchored"
+});
+/* ======================================================================
     OpenLayers/Projection.js
    ====================================================================== */
 
@@ -7198,6 +11458,1740 @@
     return point;
 };
 /* ======================================================================
+    OpenLayers/Renderer/Canvas.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.Canvas 
+ * A renderer based on the 2D 'canvas' drawing element.element
+ * 
+ * Inherits:
+ *  - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
+
+    /**
+     * Property: root
+     * {DOMElement} root element of canvas.
+     */
+    root: null,
+
+    /**
+     * Property: canvas
+     * {Canvas} The canvas context object.
+     */
+    canvas: null, 
+    
+    /**
+     * Property: features
+     * {Object} Internal object of feature/style pairs for use in redrawing the layer.
+     */
+    features: null, 
+   
+    /**
+     * Property: geometryMap
+     * {Object} Geometry -> Feature lookup table. Used by eraseGeometry to
+     *     lookup features to remove from our internal table (this.features)
+     *     when erasing geoms.
+     */
+    geometryMap: null,
+ 
+    /**
+     * Constructor: OpenLayers.Renderer.Canvas
+     *
+     * Parameters:
+     * containerID - {<String>} 
+     */
+    initialize: function(containerID) {
+        OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+        this.root = document.createElement("canvas");
+        this.container.appendChild(this.root);
+        this.canvas = this.root.getContext("2d");
+        this.features = {};
+        this.geometryMap = {};
+    },
+    
+    /** 
+     * Method: eraseGeometry
+     * Erase a geometry from the renderer. Because the Canvas renderer has
+     *     'memory' of the features that it has drawn, we have to remove the
+     *     feature so it doesn't redraw.   
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     */
+    eraseGeometry: function(geometry) {
+        this.eraseFeatures(this.features[this.geometryMap[geometry.id]][0]);
+    },
+
+    /**
+     * APIMethod: supported
+     * 
+     * Returns:
+     * {Boolean} Whether or not the browser supports the renderer class
+     */
+    supported: function() {
+        var canvas = document.createElement("canvas");
+        return !!canvas.getContext;
+    },    
+    
+    /**
+     * Method: setExtent
+     * Set the visible part of the layer.
+     *
+     * Resolution has probably changed, so we nullify the resolution 
+     * cache (this.resolution), then redraw. 
+     *
+     * Parameters:
+     * extent - {<OpenLayers.Bounds>} 
+     */
+    setExtent: function(extent) {
+        this.extent = extent.clone();
+        this.resolution = null;
+        this.redraw();
+    },
+    
+    /**
+     * Method: setSize
+     * Sets the size of the drawing surface.
+     *
+     * Once the size is updated, redraw the canvas.
+     *
+     * Parameters:
+     * size - {<OpenLayers.Size>} 
+     */
+    setSize: function(size) {
+        this.size = size.clone();
+        this.root.style.width = size.w + "px";
+        this.root.style.height = size.h + "px";
+        this.root.width = size.w;
+        this.root.height = size.h;
+        this.resolution = null;
+    },
+    
+    /**
+     * Method: drawFeature
+     * Draw the feature. Stores the feature in the features list,
+     * then redraws the layer. 
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     * style - {<Object>} 
+     */
+    drawFeature: function(feature, style) {
+        if(style == null) {
+            style = feature.style;
+        }
+        style = OpenLayers.Util.extend({
+          'fillColor': '#000000',
+          'strokeColor': '#000000',
+          'strokeWidth': 2,
+          'fillOpacity': 1,
+          'strokeOpacity': 1
+        }, style);  
+        this.features[feature.id] = [feature, style]; 
+        this.geometryMap[feature.geometry.id] = feature.id; 
+        this.redraw();
+    },
+
+
+    /** 
+     * Method: drawGeometry
+     * Used when looping (in redraw) over the features; draws
+     * the canvas. 
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} 
+     * style - {Object} 
+     * featureId - {<String>} 
+     */
+    drawGeometry: function(geometry, style) {
+        var className = geometry.CLASS_NAME;
+        if ((className == "OpenLayers.Geometry.Collection") ||
+            (className == "OpenLayers.Geometry.MultiPoint") ||
+            (className == "OpenLayers.Geometry.MultiLineString") ||
+            (className == "OpenLayers.Geometry.MultiPolygon")) {
+            for (var i = 0; i < geometry.components.length; i++) {
+                this.drawGeometry(geometry.components[i], style);
+            }
+            return;
+        };
+        switch (geometry.CLASS_NAME) {
+            case "OpenLayers.Geometry.Point":
+                this.drawPoint(geometry, style);
+                break;
+            case "OpenLayers.Geometry.LineString":
+                this.drawLineString(geometry, style);
+                break;
+            case "OpenLayers.Geometry.LinearRing":
+                this.drawLinearRing(geometry, style);
+                break;
+            case "OpenLayers.Geometry.Polygon":
+                this.drawPolygon(geometry, style);
+                break;
+            default:
+                break;
+        }
+    },
+
+    /**
+     * Method: drawExternalGraphic
+     * Called to draw External graphics. 
+     * 
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>}
+     * style    - {Object}
+     */ 
+    drawExternalGraphic: function(pt, style) {
+       var img = new Image();
+       img.src = style.externalGraphic;
+
+       var width = style.graphicWidth || style.graphicHeight;
+       var height = style.graphicHeight || style.graphicWidth;
+       width = width ? width : style.pointRadius*2;
+       height = height ? height : style.pointRadius*2;
+       var xOffset = (style.graphicXOffset != undefined) ?
+           style.graphicXOffset : -(0.5 * width);
+       var yOffset = (style.graphicYOffset != undefined) ?
+           style.graphicYOffset : -(0.5 * height);
+       var opacity = style.graphicOpacity || style.fillOpacity;
+       
+       var context = { img: img, 
+                       x: (pt[0]+xOffset), 
+                       y: (pt[1]+yOffset), 
+                       width: width, 
+                       height: height, 
+                       canvas: this.canvas };
+
+       img.onload = OpenLayers.Function.bind( function() {
+           this.canvas.drawImage(this.img, this.x, 
+                                 this.y, this.width, this.height);
+       }, context);   
+    },
+
+    /**
+     * Method: setCanvasStyle
+     * Prepare the canvas for drawing by setting various global settings.
+     *
+     * Parameters:
+     * type - {String} one of 'stroke', 'fill', or 'reset'
+     * style - {Object} Symbolizer hash
+     */
+    setCanvasStyle: function(type, style) {
+        if (type == "fill") {     
+            this.canvas.globalAlpha = style['fillOpacity'];
+            this.canvas.fillStyle = style['fillColor'];
+        } else if (type == "stroke") {  
+            this.canvas.globalAlpha = style['strokeOpacity'];
+            this.canvas.strokeStyle = style['strokeColor'];
+            this.canvas.lineWidth = style['strokeWidth'];
+        } else {
+            this.canvas.globalAlpha = 0;
+            this.canvas.lineWidth = 1;
+        }
+    },
+
+    /**
+     * Method: drawPoint
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>}
+     * style    - {Object}
+     */ 
+    drawPoint: function(geometry, style) {
+        var pt = this.getLocalXY(geometry);
+        
+        if (style.externalGraphic) {
+            this.drawExternalGraphic(pt, style);
+        } else {
+            this.setCanvasStyle("fill", style);
+            this.canvas.beginPath();
+            this.canvas.arc(pt[0], pt[1], 6, 0, Math.PI*2, true);
+            this.canvas.fill();
+            
+            this.setCanvasStyle("stroke", style);
+            this.canvas.beginPath();
+            this.canvas.arc(pt[0], pt[1], 6, 0, Math.PI*2, true);
+            this.canvas.stroke();
+            this.setCanvasStyle("reset");
+        }
+    },
+
+    /**
+     * Method: drawLineString
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>}
+     * style    - {Object}
+     */ 
+    drawLineString: function(geometry, style) {
+        this.setCanvasStyle("stroke", style);
+        this.canvas.beginPath();
+        var start = this.getLocalXY(geometry.components[0]);
+        this.canvas.moveTo(start[0], start[1]);
+        for(var i = 1; i < geometry.components.length; i++) {
+            var pt = this.getLocalXY(geometry.components[i]);
+            this.canvas.lineTo(pt[0], pt[1]);
+        }
+        this.canvas.stroke();
+        this.setCanvasStyle("reset");
+    },    
+    
+    /**
+     * Method: drawLinearRing
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>}
+     * style    - {Object}
+     */ 
+    drawLinearRing: function(geometry, style) {
+        this.setCanvasStyle("fill", style);
+        this.canvas.beginPath();
+        var start = this.getLocalXY(geometry.components[0]);
+        this.canvas.moveTo(start[0], start[1]);
+        for(var i = 1; i < geometry.components.length - 1 ; i++) {
+            var pt = this.getLocalXY(geometry.components[i]);
+            this.canvas.lineTo(pt[0], pt[1]);
+        }
+        this.canvas.fill();
+        
+        var oldWidth = this.canvas.lineWidth; 
+        this.setCanvasStyle("stroke", style);
+        this.canvas.beginPath();
+        var start = this.getLocalXY(geometry.components[0]);
+        this.canvas.moveTo(start[0], start[1]);
+        for(var i = 1; i < geometry.components.length; i++) {
+            var pt = this.getLocalXY(geometry.components[i]);
+            this.canvas.lineTo(pt[0], pt[1]);
+        }
+        this.canvas.stroke();
+        this.setCanvasStyle("reset");
+    },    
+    
+    /**
+     * Method: drawPolygon
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>}
+     * style    - {Object}
+     */ 
+    drawPolygon: function(geometry, style) {
+        this.drawLinearRing(geometry.components[0], style);
+        for (var i = 1; i < geometry.components.length; i++) {
+            this.drawLinearRing(geometry.components[i], {
+                fillOpacity: 0, 
+                strokeWidth: 0, 
+                strokeOpacity: 0, 
+                strokeColor: '#000000', 
+                fillColor: '#000000'}
+            ); // inner rings are 'empty'  
+        }
+    },
+
+    /**
+     * Method: getLocalXY
+     * transform geographic xy into pixel xy
+     *
+     * Parameters: 
+     * point - {<OpenLayers.Geometry.Point>}
+     */
+    getLocalXY: function(point) {
+        var resolution = this.getResolution();
+        var extent = this.extent;
+        var x = (point.x / resolution + (-extent.left / resolution));
+        var y = ((extent.top / resolution) - point.y / resolution);
+        return [x, y];
+    },
+        
+    /**
+     * Method: clear
+     * Clear all vectors from the renderer.
+     * virtual function.
+     */    
+    clear: function() {
+        this.canvas.clearRect(0, 0, this.root.width, this.root.height);
+    },
+
+    /**
+     * Method: getFeatureIdFromEvent
+     * Returns a feature id from an event on the renderer.  
+     * 
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     *
+     * Returns:
+     * {String} A feature id or null.
+     */
+    getFeatureIdFromEvent: function(evt) {
+        var loc = this.map.getLonLatFromPixel(evt.xy);
+        var resolution = this.getResolution();
+        var bounds = new OpenLayers.Bounds(loc.lon - resolution * 5, 
+                                           loc.lat - resolution * 5, 
+                                           loc.lon + resolution * 5, 
+                                           loc.lat + resolution * 5);
+        var geom = bounds.toGeometry();
+        for (var feat in this.features) {
+            if (!this.features.hasOwnProperty(feat)) { continue; }
+            if (this.features[feat][0].geometry.intersects(geom)) {
+                return feat;
+            }
+        }   
+        return null;
+    },
+    
+    /**
+     * Method: eraseFeatures 
+     * This is called by the layer to erase features; removes the feature from
+     *     the list, then redraws the layer.
+     * 
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    eraseFeatures: function(features) {
+        if(!(features instanceof Array)) {
+            features = [features];
+        }
+        for(var i=0; i<features.length; ++i) {
+            delete this.features[features[i].id];
+        }
+        this.redraw();
+    },
+
+    /**
+     * Method: redraw
+     * The real 'meat' of the function: any time things have changed,
+     *     redraw() can be called to loop over all the data and (you guessed
+     *     it) redraw it.  Unlike Elements-based Renderers, we can't interact
+     *     with things once they're drawn, to remove them, for example, so
+     *     instead we have to just clear everything and draw from scratch.
+     */
+    redraw: function() {
+        if (!this.locked) {
+            this.clear();
+            for (var id in this.features) {
+                if (!this.features.hasOwnProperty(id)) { continue; }
+                if (!this.features[id][0].geometry) { continue; }
+                this.drawGeometry(this.features[id][0].geometry, this.features[id][1]);
+            }
+        }    
+    },
+
+    CLASS_NAME: "OpenLayers.Renderer.Canvas"
+});
+/* ======================================================================
+    OpenLayers/Renderer/Elements.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.ElementsIndexer
+ * This class takes care of figuring out which order elements should be
+ *     placed in the DOM based on given indexing methods. 
+ */
+OpenLayers.ElementsIndexer = OpenLayers.Class({
+   
+    /**
+     * Property: maxZIndex
+     * {Integer} This is the largest-most z-index value for a node
+     *     contained within the indexer.
+     */
+    maxZIndex: null,
+    
+    /**
+     * Property: order
+     * {Array<String>} This is an array of node id's stored in the
+     *     order that they should show up on screen. Id's higher up in the
+     *     array (higher array index) represent nodes with higher z-indeces.
+     */
+    order: null, 
+    
+    /**
+     * Property: indices
+     * {Object} This is a hash that maps node ids to their z-index value
+     *     stored in the indexer. This is done to make finding a nodes z-index 
+     *     value O(1).
+     */
+    indices: null,
+    
+    /**
+     * Property: compare
+     * {Function} This is the function used to determine placement of
+     *     of a new node within the indexer. If null, this defaults to to
+     *     the Z_ORDER_DRAWING_ORDER comparison method.
+     */
+    compare: null,
+   
+    /**
+     * APIMethod: initialize
+     * Create a new indexer with 
+     * 
+     * Parameters:
+     * yOrdering - {Boolean} Whether to use y-ordering.
+     */
+    initialize: function(yOrdering) {
+
+        this.compare = yOrdering ? 
+            OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
+            OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
+            
+        this.order = [];
+        this.indices = {};
+        this.maxZIndex = 0;
+    },
+    
+    /**
+     * APIMethod: insert
+     * Insert a new node into the indexer. In order to find the correct 
+     *     positioning for the node to be inserted, this method uses a binary 
+     *     search. This makes inserting O(log(n)). 
+     * 
+     * Parameters:
+     * newNode - {DOMElement} The new node to be inserted.
+     * 
+     * Returns
+     * {DOMElement} the node before which we should insert our newNode, or
+     *     null if newNode can just be appended.
+     */
+    insert: function(newNode) {
+        // If the node is known to the indexer, remove it so we can
+        // recalculate where it should go.
+        if (this.exists(newNode)) {
+            this.remove(newNode);
+        }
+        
+        var nodeId = newNode.id;
+        
+        this.determineZIndex(newNode);       
+
+        var leftIndex = -1;
+        var rightIndex = this.order.length;
+        var middle;
+
+        while (rightIndex - leftIndex > 1) {
+            middle = parseInt((leftIndex + rightIndex) / 2);
+            
+            var placement = this.compare(this, newNode,
+                OpenLayers.Util.getElement(this.order[middle]));
+            
+            if (placement > 0) {
+                leftIndex = middle;
+            } else {
+                rightIndex = middle;
+            } 
+        }
+        
+        this.order.splice(rightIndex, 0, nodeId);
+        this.indices[nodeId] = this.getZIndex(newNode);
+        
+        // If the new node should be before another in the index
+        // order, return the node before which we have to insert the new one;
+        // else, return null to indicate that the new node can be appended.
+        var nextIndex = rightIndex + 1;
+        return nextIndex < this.order.length ?
+                OpenLayers.Util.getElement(this.order[nextIndex]) : null;
+    },
+    
+    /**
+     * APIMethod: remove
+     * 
+     * Parameters:
+     * node - {DOMElement} The node to be removed.
+     */
+    remove: function(node) {
+        var nodeId = node.id;
+        var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
+        if (arrayIndex >= 0) {
+            // Remove it from the order array, as well as deleting the node
+            // from the indeces hash.
+            this.order.splice(arrayIndex, 1);
+            delete this.indices[nodeId];
+            
+            // Reset the maxium z-index based on the last item in the 
+            // order array.
+            if (this.order.length > 0) {
+                var lastId = this.order[this.order.length - 1];
+                this.maxZIndex = this.indices[lastId];
+            } else {
+                this.maxZIndex = 0;
+            }
+        }
+    },
+    
+    /**
+     * APIMethod: clear
+     */
+    clear: function() {
+        this.order = [];
+        this.indices = {};
+        this.maxZIndex = 0;
+    },
+    
+    /**
+     * APIMethod: exists
+     *
+     * Parameters:
+     * node- {DOMElement} The node to test for existence.
+     *
+     * Returns:
+     * {Boolean} Whether or not the node exists in the indexer?
+     */
+    exists: function(node) {
+        return (this.indices[node.id] != null);
+    },
+
+    /**
+     * APIMethod: getZIndex
+     * Get the z-index value for the current node from the node data itself.
+     * 
+     * Parameters:
+     * node - {DOMElement} The node whose z-index to get.
+     * 
+     * Returns:
+     * {Integer} The z-index value for the specified node (from the node 
+     *     data itself).
+     */
+    getZIndex: function(node) {
+        return node._style.graphicZIndex;  
+    },
+    
+    /**
+     * Method: determineZIndex
+     * Determine the z-index for the current node if there isn't one, 
+     *     and set the maximum value if we've found a new maximum.
+     * 
+     * Parameters:
+     * node - {DOMElement} 
+     */
+    determineZIndex: function(node) {
+        var zIndex = node._style.graphicZIndex;
+        
+        // Everything must have a zIndex. If none is specified,
+        // this means the user *must* (hint: assumption) want this
+        // node to succomb to drawing order. To enforce drawing order
+        // over all indexing methods, we'll create a new z-index that's
+        // greater than any currently in the indexer.
+        if (zIndex == null) {
+            zIndex = this.maxZIndex;
+            node._style.graphicZIndex = zIndex; 
+        } else if (zIndex > this.maxZIndex) {
+            this.maxZIndex = zIndex;
+        }
+    },
+    
+    CLASS_NAME: "OpenLayers.ElementsIndexer"
+});
+
+/**
+ * Namespace: OpenLayers.ElementsIndexer.IndexingMethods
+ * These are the compare methods for figuring out where a new node should be 
+ *     placed within the indexer. These methods are very similar to general 
+ *     sorting methods in that they return -1, 0, and 1 to specify the 
+ *     direction in which new nodes fall in the ordering.
+ */
+OpenLayers.ElementsIndexer.IndexingMethods = {
+    
+    /**
+     * Method: Z_ORDER
+     * This compare method is used by other comparison methods.
+     *     It can be used individually for ordering, but is not recommended,
+     *     because it doesn't subscribe to drawing order.
+     * 
+     * Parameters:
+     * indexer - {<OpenLayers.ElementsIndexer>}
+     * newNode - {DOMElement}
+     * nextNode - {DOMElement}
+     * 
+     * Returns:
+     * {Integer}
+     */
+    Z_ORDER: function(indexer, newNode, nextNode) {
+        var newZIndex = indexer.getZIndex(newNode);
+
+        var returnVal = 0;
+        if (nextNode) {
+            var nextZIndex = indexer.getZIndex(nextNode);
+            returnVal = newZIndex - nextZIndex; 
+        }
+        
+        return returnVal;
+    },
+
+    /**
+     * APIMethod: Z_ORDER_DRAWING_ORDER
+     * This method orders nodes by their z-index, but does so in a way
+     *     that, if there are other nodes with the same z-index, the newest 
+     *     drawn will be the front most within that z-index. This is the 
+     *     default indexing method.
+     * 
+     * Parameters:
+     * indexer - {<OpenLayers.ElementsIndexer>}
+     * newNode - {DOMElement}
+     * nextNode - {DOMElement}
+     * 
+     * Returns:
+     * {Integer}
+     */
+    Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
+        var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+            indexer, 
+            newNode, 
+            nextNode
+        );
+        
+        // Make Z_ORDER subscribe to drawing order by pushing it above
+        // all of the other nodes with the same z-index.
+        if (nextNode && returnVal == 0) {
+            returnVal = 1;
+        }
+        
+        return returnVal;
+    },
+
+    /**
+     * APIMethod: Z_ORDER_Y_ORDER
+     * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
+     *     best describes which ordering methods have precedence (though, the 
+     *     name would be too long). This method orders nodes by their z-index, 
+     *     but does so in a way that, if there are other nodes with the same 
+     *     z-index, the nodes with the lower y position will be "closer" than 
+     *     those with a higher y position. If two nodes have the exact same y 
+     *     position, however, then this method will revert to using drawing  
+     *     order to decide placement.
+     * 
+     * Parameters:
+     * indexer - {<OpenLayers.ElementsIndexer>}
+     * newNode - {DOMElement}
+     * nextNode - {DOMElement}
+     * 
+     * Returns:
+     * {Integer}
+     */
+    Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
+        var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+            indexer, 
+            newNode, 
+            nextNode
+        );
+        
+        if (nextNode && returnVal == 0) {
+            var newLat = newNode._geometry.getBounds().bottom;
+            var nextLat = nextNode._geometry.getBounds().bottom;
+            
+            var result = nextLat - newLat;
+            returnVal = (result ==0) ? 1 : result;
+        }
+        
+        return returnVal;       
+    }
+};
+
+/**
+ * Class: OpenLayers.Renderer.Elements
+ * This is another virtual class in that it should never be instantiated by 
+ *  itself as a Renderer. It exists because there is *tons* of shared 
+ *  functionality between different vector libraries which use nodes/elements
+ *  as a base for rendering vectors. 
+ * 
+ * The highlevel bits of code that are implemented here are the adding and 
+ *  removing of geometries, which is essentially the same for any 
+ *  element-based renderer. The details of creating each node and drawing the
+ *  paths are of course different, but the machinery is the same. 
+ * 
+ * Inherits:
+ *  - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
+
+    /**
+     * Property: rendererRoot
+     * {DOMElement}
+     */
+    rendererRoot: null,
+    
+    /**
+     * Property: root
+     * {DOMElement}
+     */
+    root: null,
+
+    /**
+     * Property: xmlns
+     * {String}
+     */    
+    xmlns: null,
+    
+    /**
+     * Property: Indexer
+     * {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer 
+     *     created upon initialization if the zIndexing or yOrdering options
+     *     passed to this renderer's constructor are set to true.
+     */
+    indexer: null, 
+    
+    /**
+     * Constant: BACKGROUND_ID_SUFFIX
+     * {String}
+     */
+    BACKGROUND_ID_SUFFIX: "_background",
+    
+    /**
+     * Property: minimumSymbolizer
+     * {Object}
+     */
+    minimumSymbolizer: {
+        strokeLinecap: "round",
+        strokeOpacity: 1,
+        strokeDashstyle: "solid",
+        fillOpacity: 1,
+        pointRadius: 0
+    },
+    
+    /**
+     * Constructor: OpenLayers.Renderer.Elements
+     * 
+     * Parameters:
+     * containerID - {String}
+     * options - {Object} options for this renderer. Supported options are:
+     *     * yOrdering - {Boolean} Whether to use y-ordering
+     *     * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
+     *         if yOrdering is set to true.
+     */
+    initialize: function(containerID, options) {
+        OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+
+        this.rendererRoot = this.createRenderRoot();
+        this.root = this.createRoot();
+        
+        this.rendererRoot.appendChild(this.root);
+        this.container.appendChild(this.rendererRoot);
+        
+        if(options && (options.zIndexing || options.yOrdering)) {
+            this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
+        }
+    },
+    
+    /**
+     * Method: destroy
+     */
+    destroy: function() {
+
+        this.clear(); 
+
+        this.rendererRoot = null;
+        this.root = null;
+        this.xmlns = null;
+
+        OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * Method: clear
+     * Remove all the elements from the root
+     */    
+    clear: function() {
+        if (this.root) {
+            while (this.root.childNodes.length > 0) {
+                this.root.removeChild(this.root.firstChild);
+            }
+        }
+        if (this.indexer) {
+            this.indexer.clear();
+        }
+    },
+
+    /** 
+     * Method: getNodeType
+     * This function is in charge of asking the specific renderer which type
+     *     of node to create for the given geometry and style. All geometries
+     *     in an Elements-based renderer consist of one node and some
+     *     attributes. We have the nodeFactory() function which creates a node
+     *     for us, but it takes a 'type' as input, and that is precisely what
+     *     this function tells us.  
+     *  
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * 
+     * Returns:
+     * {String} The corresponding node type for the specified geometry
+     */
+    getNodeType: function(geometry, style) { },
+
+    /** 
+     * Method: drawGeometry 
+     * Draw the geometry, creating new nodes, setting paths, setting style,
+     *     setting featureId on the node.  This method should only be called
+     *     by the renderer itself.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * featureId - {String}
+     * 
+     * Returns:
+     * {Boolean} true if the geometry has been drawn completely; null if
+     *     incomplete; false otherwise
+     */
+    drawGeometry: function(geometry, style, featureId) {
+        var className = geometry.CLASS_NAME;
+        var rendered = true;
+        if ((className == "OpenLayers.Geometry.Collection") ||
+            (className == "OpenLayers.Geometry.MultiPoint") ||
+            (className == "OpenLayers.Geometry.MultiLineString") ||
+            (className == "OpenLayers.Geometry.MultiPolygon")) {
+            for (var i = 0, len=geometry.components.length; i<len; i++) {
+                rendered = rendered && this.drawGeometry(
+                    geometry.components[i], style, featureId);
+            }
+            return rendered;
+        };
+
+        rendered = false;
+        if (style.display != "none") {
+            if (style.backgroundGraphic) {
+                this.redrawBackgroundNode(geometry.id, geometry, style,
+                    featureId);
+            }
+            rendered = this.redrawNode(geometry.id, geometry, style,
+                featureId);
+        }
+        if (rendered == false) {
+            var node = document.getElementById(geometry.id);
+            if (node) {
+                if (node._style.backgroundGraphic) {
+                    node.parentNode.removeChild(document.getElementById(
+                        geometry.id + this.BACKGROUND_ID_SUFFIX));
+                }
+                node.parentNode.removeChild(node);
+            }
+        }
+        return rendered;
+    },
+    
+    /**
+     * Method: redrawNode
+     * 
+     * Parameters:
+     * id - {String}
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * featureId - {String}
+     * 
+     * Returns:
+     * {Boolean} true if the complete geometry could be drawn, null if parts of
+     *     the geometry could not be drawn, false otherwise
+     */
+    redrawNode: function(id, geometry, style, featureId) {
+        // Get the node if it's already on the map.
+        var currentNode = OpenLayers.Util.getElement(id);
+        
+        // Create a new node, or use the current one if it's
+        // already there.
+        var newNode;
+        if (!currentNode) {
+            var nodeType = this.getNodeType(geometry, style);
+            newNode = this.createNode(nodeType, id);
+        } else {
+            newNode = currentNode;
+        }
+        
+        // Set the data for the node, then draw it.
+        newNode._featureId = featureId;
+        newNode._geometry = geometry;
+        newNode._geometryClass = geometry.CLASS_NAME;
+        newNode._style = style;
+
+        var drawResult = this.drawGeometryNode(newNode, geometry, style);
+        if(drawResult === false) {
+            return false;
+        }
+         
+        newNode = drawResult.node;
+        
+        // Insert the node into the indexer so it can show us where to
+        // place it. Note that this operation is O(log(n)). If there's a
+        // performance problem (when dragging, for instance) this is
+        // likely where it would be.
+        var insert = this.indexer ? this.indexer.insert(newNode) : null;
+
+        if(insert) {
+            this.root.insertBefore(newNode, insert);
+        } else {
+            this.root.appendChild(newNode);
+        }
+        
+        this.postDraw(newNode);
+        
+        return drawResult.complete;
+    },
+    
+    /**
+     * Method: redrawBackgroundNode
+     * Redraws the node using special 'background' style properties. Basically
+     *     just calls redrawNode(), but instead of directly using the 
+     *     'externalGraphic', 'graphicXOffset', 'graphicYOffset', and 
+     *     'graphicZIndex' properties directly from the specified 'style' 
+     *     parameter, we create a new style object and set those properties 
+     *     from the corresponding 'background'-prefixed properties from 
+     *     specified 'style' parameter.
+     * 
+     * Parameters:
+     * id - {String}
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * featureId - {String}
+     * 
+     * Returns:
+     * {Boolean} true if the complete geometry could be drawn, null if parts of
+     *     the geometry could not be drawn, false otherwise
+     */
+    redrawBackgroundNode: function(id, geometry, style, featureId) {
+        var backgroundStyle = OpenLayers.Util.extend({}, style);
+        
+        // Set regular style attributes to apply to the background styles.
+        backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
+        backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
+        backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
+        backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
+        
+        // Erase background styles.
+        backgroundStyle.backgroundGraphic = null;
+        backgroundStyle.backgroundXOffset = null;
+        backgroundStyle.backgroundYOffset = null;
+        backgroundStyle.backgroundGraphicZIndex = null;
+        
+        return this.redrawNode(
+            id + this.BACKGROUND_ID_SUFFIX, 
+            geometry, 
+            backgroundStyle, 
+            null
+        );
+    },
+
+    /**
+     * Method: drawGeometryNode
+     * Given a node, draw a geometry on the specified layer.
+     *     node and geometry are required arguments, style is optional.
+     *     This method is only called by the render itself.
+     *
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * 
+     * Returns:
+     * {Object} a hash with properties "node" (the drawn node) and "complete"
+     *     (null if parts of the geometry could not be drawn, false if nothing
+     *     could be drawn)
+     */
+    drawGeometryNode: function(node, geometry, style) {
+        style = style || node._style;
+        OpenLayers.Util.applyDefaults(style, this.minimumSymbolizer);
+
+        var options = {
+            'isFilled': true,
+            'isStroked': !!style.strokeWidth
+        };
+        var drawn;
+        switch (geometry.CLASS_NAME) {
+            case "OpenLayers.Geometry.Point":
+                drawn = this.drawPoint(node, geometry);
+                break;
+            case "OpenLayers.Geometry.LineString":
+                options.isFilled = false;
+                drawn = this.drawLineString(node, geometry);
+                break;
+            case "OpenLayers.Geometry.LinearRing":
+                drawn = this.drawLinearRing(node, geometry);
+                break;
+            case "OpenLayers.Geometry.Polygon":
+                drawn = this.drawPolygon(node, geometry);
+                break;
+            case "OpenLayers.Geometry.Surface":
+                drawn = this.drawSurface(node, geometry);
+                break;
+            case "OpenLayers.Geometry.Rectangle":
+                drawn = this.drawRectangle(node, geometry);
+                break;
+            default:
+                break;
+        }
+
+        node._style = style; 
+        node._options = options; 
+
+        //set style
+        //TBD simplify this
+        if (drawn != false) {
+            return {
+                node: this.setStyle(node, style, options, geometry),
+                complete: drawn
+            };
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: postDraw
+     * Things that have do be done after the geometry node is appended
+     *     to its parent node. To be overridden by subclasses.
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     */
+    postDraw: function(node) {},
+    
+    /**
+     * Method: drawPoint
+     * Virtual function for drawing Point Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the point
+     */ 
+    drawPoint: function(node, geometry) {},
+
+    /**
+     * Method: drawLineString
+     * Virtual function for drawing LineString Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components of
+     *     the linestring, or false if nothing could be drawn
+     */ 
+    drawLineString: function(node, geometry) {},
+
+    /**
+     * Method: drawLinearRing
+     * Virtual function for drawing LinearRing Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components
+     *     of the linear ring, or false if nothing could be drawn
+     */ 
+    drawLinearRing: function(node, geometry) {},
+
+    /**
+     * Method: drawPolygon
+     * Virtual function for drawing Polygon Geometry. 
+     *    Should be implemented by subclasses.
+     *    This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components
+     *     of the polygon, or false if nothing could be drawn
+     */ 
+    drawPolygon: function(node, geometry) {},
+
+    /**
+     * Method: drawRectangle
+     * Virtual function for drawing Rectangle Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the rectangle
+     */ 
+    drawRectangle: function(node, geometry) {},
+
+    /**
+     * Method: drawCircle
+     * Virtual function for drawing Circle Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the circle
+     */ 
+    drawCircle: function(node, geometry) {},
+
+    /**
+     * Method: drawSurface
+     * Virtual function for drawing Surface Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the surface
+     */ 
+    drawSurface: function(node, geometry) {},
+
+    /**
+     * Method: getFeatureIdFromEvent
+     * 
+     * Parameters:
+     * evt - {Object} An <OpenLayers.Event> object
+     *
+     * Returns:
+     * {<OpenLayers.Geometry>} A geometry from an event that 
+     *     happened on a layer.
+     */
+    getFeatureIdFromEvent: function(evt) {
+        var target = evt.target;
+        var useElement = target && target.correspondingUseElement;
+        var node = useElement ? useElement : (target || evt.srcElement);
+        var featureId = node._featureId;
+        return featureId;
+    },
+
+    /** 
+     * Method: eraseGeometry
+     * Erase a geometry from the renderer. In the case of a multi-geometry, 
+     *     we cycle through and recurse on ourselves. Otherwise, we look for a 
+     *     node with the geometry.id, destroy its geometry, and remove it from
+     *     the DOM.
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     */
+    eraseGeometry: function(geometry) {
+        if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
+            (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
+            (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
+            (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
+            for (var i=0, len=geometry.components.length; i<len; i++) {
+                this.eraseGeometry(geometry.components[i]);
+            }
+        } else {    
+            var element = OpenLayers.Util.getElement(geometry.id);
+            if (element && element.parentNode) {
+                if (element.geometry) {
+                    element.geometry.destroy();
+                    element.geometry = null;
+                }
+                element.parentNode.removeChild(element);
+
+                if (this.indexer) {
+                    this.indexer.remove(element);
+                    
+                    var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
+                    var bElem = OpenLayers.Util.getElement(backgroundId);
+                    if (bElem && bElem.parentNode) {
+                        // No need to destroy the geometry since the element and the background
+                        // node share the same geometry.
+                        bElem.parentNode.removeChild(bElem);
+                    }
+                }
+            }
+        }
+    },
+
+    /** 
+     * Method: nodeFactory
+     * Create new node of the specified type, with the (optional) specified id.
+     * 
+     * If node already exists with same ID and a different type, we remove it
+     *     and then call ourselves again to recreate it.
+     * 
+     * Parameters:
+     * id - {String}
+     * type - {String} type Kind of node to draw.
+     * 
+     * Returns:
+     * {DOMElement} A new node of the given type and id.
+     */
+    nodeFactory: function(id, type) {
+        var node = OpenLayers.Util.getElement(id);
+        if (node) {
+            if (!this.nodeTypeCompare(node, type)) {
+                node.parentNode.removeChild(node);
+                node = this.nodeFactory(id, type);
+            }
+        } else {
+            node = this.createNode(type, id);
+        }
+        return node;
+    },
+    
+    /** 
+     * Method: createNode
+     * 
+     * Parameters:
+     * type - {String} Kind of node to draw.
+     * id - {String} Id for node.
+     * 
+     * Returns:
+     * {DOMElement} A new node of the given type and id.
+     *     This function must be overridden by subclasses.
+     */
+    createNode: function(type, id) {},
+
+    /**
+     * Method: isComplexSymbol
+     * Determines if a symbol cannot be rendered using drawCircle
+     * 
+     * Parameters:
+     * graphicName - {String}
+     * 
+     * Returns
+     * {Boolean} true if the symbol is complex, false if not
+     */
+    isComplexSymbol: function(graphicName) {
+        return (graphicName != "circle") && !!graphicName;
+    },
+
+    CLASS_NAME: "OpenLayers.Renderer.Elements"
+});
+
+
+/**
+ * Constant: OpenLayers.Renderer.symbol
+ * Coordinate arrays for well known (named) symbols.
+ */
+OpenLayers.Renderer.symbol = {
+    "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
+            303,215, 231,161, 321,161, 350,75],
+    "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4,
+            4,0],
+    "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0],
+    "square": [0,0, 0,1, 1,1, 1,0, 0,0],
+    "triangle": [0,10, 10,10, 5,0, 0,10]
+};
+/* ======================================================================
+    OpenLayers/Request/XMLHttpRequest.js
+   ====================================================================== */
+
+// 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @requires OpenLayers/Request.js
+ */
+
+(function () {
+
+    // Save reference to earlier defined object implementation (if any)
+    var oXMLHttpRequest    = window.XMLHttpRequest;
+
+    // Define on browser type
+    var bGecko    = !!window.controllers,
+        bIE        = window.document.all && !window.opera;
+
+    // Constructor
+    function cXMLHttpRequest() {
+        this._object    = oXMLHttpRequest ? new oXMLHttpRequest : new window.ActiveXObject('Microsoft.XMLHTTP');
+    };
+
+    // BUGFIX: Firefox with Firebug installed would break pages if not executed
+    if (bGecko && oXMLHttpRequest.wrapped)
+        cXMLHttpRequest.wrapped    = oXMLHttpRequest.wrapped;
+
+    // Constants
+    cXMLHttpRequest.UNSENT                = 0;
+    cXMLHttpRequest.OPENED                = 1;
+    cXMLHttpRequest.HEADERS_RECEIVED    = 2;
+    cXMLHttpRequest.LOADING                = 3;
+    cXMLHttpRequest.DONE                = 4;
+
+    // Public Properties
+    cXMLHttpRequest.prototype.readyState    = cXMLHttpRequest.UNSENT;
+    cXMLHttpRequest.prototype.responseText    = "";
+    cXMLHttpRequest.prototype.responseXML    = null;
+    cXMLHttpRequest.prototype.status        = 0;
+    cXMLHttpRequest.prototype.statusText    = "";
+
+    // Instance-level Events Handlers
+    cXMLHttpRequest.prototype.onreadystatechange    = null;
+
+    // Class-level Events Handlers
+    cXMLHttpRequest.onreadystatechange    = null;
+    cXMLHttpRequest.onopen                = null;
+    cXMLHttpRequest.onsend                = null;
+    cXMLHttpRequest.onabort                = null;
+
+    // Public Methods
+    cXMLHttpRequest.prototype.open    = function(sMethod, sUrl, bAsync, sUser, sPassword) {
+
+        // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
+        this._async        = bAsync;
+
+        // Set the onreadystatechange handler
+        var oRequest    = this,
+            nState        = this.readyState;
+
+        // BUGFIX: IE - memory leak on page unload (inter-page leak)
+        if (bIE) {
+            var fOnUnload    = function() {
+                if (oRequest._object.readyState != cXMLHttpRequest.DONE)
+                    fCleanTransport(oRequest);
+            };
+            if (bAsync)
+                window.attachEvent("onunload", fOnUnload);
+        }
+
+        this._object.onreadystatechange    = function() {
+            if (bGecko && !bAsync)
+                return;
+
+            // Synchronize state
+            oRequest.readyState        = oRequest._object.readyState;
+
+            //
+            fSynchronizeValues(oRequest);
+
+            // BUGFIX: Firefox fires unneccesary DONE when aborting
+            if (oRequest._aborted) {
+                // Reset readyState to UNSENT
+                oRequest.readyState    = cXMLHttpRequest.UNSENT;
+
+                // Return now
+                return;
+            }
+
+            if (oRequest.readyState == cXMLHttpRequest.DONE) {
+                //
+                fCleanTransport(oRequest);
+// Uncomment this block if you need a fix for IE cache
+/*
+                // BUGFIX: IE - cache issue
+                if (!oRequest._object.getResponseHeader("Date")) {
+                    // Save object to cache
+                    oRequest._cached    = oRequest._object;
+
+                    // Instantiate a new transport object
+                    cXMLHttpRequest.call(oRequest);
+
+                    // Re-send request
+                    oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+                    oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
+                    // Copy headers set
+                    if (oRequest._headers)
+                        for (var sHeader in oRequest._headers)
+                            if (typeof oRequest._headers[sHeader] == "string")    // Some frameworks prototype objects with functions
+                                oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
+
+                    oRequest._object.onreadystatechange    = function() {
+                        // Synchronize state
+                        oRequest.readyState        = oRequest._object.readyState;
+
+                        if (oRequest._aborted) {
+                            //
+                            oRequest.readyState    = cXMLHttpRequest.UNSENT;
+
+                            // Return
+                            return;
+                        }
+
+                        if (oRequest.readyState == cXMLHttpRequest.DONE) {
+                            // Clean Object
+                            fCleanTransport(oRequest);
+
+                            // get cached request
+                            if (oRequest.status == 304)
+                                oRequest._object    = oRequest._cached;
+
+                            //
+                            delete oRequest._cached;
+
+                            //
+                            fSynchronizeValues(oRequest);
+
+                            //
+                            fReadyStateChange(oRequest);
+
+                            // BUGFIX: IE - memory leak in interrupted
+                            if (bIE && bAsync)
+                                window.detachEvent("onunload", fOnUnload);
+                        }
+                    };
+                    oRequest._object.send(null);
+
+                    // Return now - wait untill re-sent request is finished
+                    return;
+                };
+*/
+                // BUGFIX: IE - memory leak in interrupted
+                if (bIE && bAsync)
+                    window.detachEvent("onunload", fOnUnload);
+            }
+
+            // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
+            if (nState != oRequest.readyState)
+                fReadyStateChange(oRequest);
+
+            nState    = oRequest.readyState;
+        };
+
+        // Add method sniffer
+        if (cXMLHttpRequest.onopen)
+            cXMLHttpRequest.onopen.apply(this, arguments);
+
+        this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+
+        // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
+        if (!bAsync && bGecko) {
+            this.readyState    = cXMLHttpRequest.OPENED;
+
+            fReadyStateChange(this);
+        }
+    };
+    cXMLHttpRequest.prototype.send    = function(vData) {
+        // Add method sniffer
+        if (cXMLHttpRequest.onsend)
+            cXMLHttpRequest.onsend.apply(this, arguments);
+
+        // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
+        // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
+        // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
+        if (vData && vData.nodeType) {
+            vData    = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
+            if (!this._headers["Content-Type"])
+                this._object.setRequestHeader("Content-Type", "application/xml");
+        }
+
+        this._object.send(vData);
+
+        // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
+        if (bGecko && !this._async) {
+            this.readyState    = cXMLHttpRequest.OPENED;
+
+            // Synchronize state
+            fSynchronizeValues(this);
+
+            // Simulate missing states
+            while (this.readyState < cXMLHttpRequest.DONE) {
+                this.readyState++;
+                fReadyStateChange(this);
+                // Check if we are aborted
+                if (this._aborted)
+                    return;
+            }
+        }
+    };
+    cXMLHttpRequest.prototype.abort    = function() {
+        // Add method sniffer
+        if (cXMLHttpRequest.onabort)
+            cXMLHttpRequest.onabort.apply(this, arguments);
+
+        // BUGFIX: Gecko - unneccesary DONE when aborting
+        if (this.readyState > cXMLHttpRequest.UNSENT)
+            this._aborted    = true;
+
+        this._object.abort();
+
+        // BUGFIX: IE - memory leak
+        fCleanTransport(this);
+    };
+    cXMLHttpRequest.prototype.getAllResponseHeaders    = function() {
+        return this._object.getAllResponseHeaders();
+    };
+    cXMLHttpRequest.prototype.getResponseHeader    = function(sName) {
+        return this._object.getResponseHeader(sName);
+    };
+    cXMLHttpRequest.prototype.setRequestHeader    = function(sName, sValue) {
+        // BUGFIX: IE - cache issue
+        if (!this._headers)
+            this._headers    = {};
+        this._headers[sName]    = sValue;
+
+        return this._object.setRequestHeader(sName, sValue);
+    };
+    cXMLHttpRequest.prototype.toString    = function() {
+        return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
+    };
+    cXMLHttpRequest.toString    = function() {
+        return '[' + "XMLHttpRequest" + ']';
+    };
+
+    // Helper function
+    function fReadyStateChange(oRequest) {
+        // Execute onreadystatechange
+        if (oRequest.onreadystatechange)
+            oRequest.onreadystatechange.apply(oRequest);
+
+        // Sniffing code
+        if (cXMLHttpRequest.onreadystatechange)
+            cXMLHttpRequest.onreadystatechange.apply(oRequest);
+    };
+
+    function fGetDocument(oRequest) {
+        var oDocument    = oRequest.responseXML;
+        // Try parsing responseText
+        if (bIE && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
+            oDocument    = new ActiveXObject('Microsoft.XMLDOM');
+            oDocument.loadXML(oRequest.responseText);
+        }
+        // Check if there is no error in document
+        if (oDocument)
+            if ((bIE && oDocument.parseError != 0) || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror"))
+                return null;
+        return oDocument;
+    };
+
+    function fSynchronizeValues(oRequest) {
+        try {    oRequest.responseText    = oRequest._object.responseText;    } catch (e) {}
+        try {    oRequest.responseXML    = fGetDocument(oRequest._object);    } catch (e) {}
+        try {    oRequest.status            = oRequest._object.status;            } catch (e) {}
+        try {    oRequest.statusText        = oRequest._object.statusText;        } catch (e) {}
+    };
+
+    function fCleanTransport(oRequest) {
+        // BUGFIX: IE - memory leak (on-page leak)
+        oRequest._object.onreadystatechange    = new window.Function;
+
+        // Delete private properties
+        delete oRequest._headers;
+    };
+
+    // Internet Explorer 5.0 (missing apply)
+    if (!window.Function.prototype.apply) {
+        window.Function.prototype.apply    = function(oRequest, oArguments) {
+            if (!oArguments)
+                oArguments    = [];
+            oRequest.__func    = this;
+            oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
+            delete oRequest.__func;
+        };
+    };
+
+    // Register new object with window
+    /**
+     * Class: OpenLayers.Request.XMLHttpRequest
+     * Standard-compliant (W3C) cross-browser implementation of the
+     *     XMLHttpRequest object.  From
+     *     http://code.google.com/p/xmlhttprequest/.
+     */
+    OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest;
+})();
+/* ======================================================================
+    OpenLayers/Strategy/Fixed.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Fixed
+ * A simple strategy that requests features once and never requests new data.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Fixed = OpenLayers.Class(OpenLayers.Strategy, {
+
+    /**
+     * Constructor: OpenLayers.Strategy.Fixed
+     * Create a new Fixed strategy.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Strategy.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: destroy
+     * Clean up the strategy.
+     */
+    destroy: function() {
+        OpenLayers.Strategy.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * Method: activate
+     * Activate the strategy: reads all features from the protocol and add them 
+     * to the layer.
+     *
+     * Returns:
+     * {Boolean} True if the strategy was successfully activated or false if
+     *      the strategy was already active.
+     */
+    activate: function() {
+        if(OpenLayers.Strategy.prototype.activate.apply(this, arguments)) {
+            this.layer.protocol.read({
+                callback: this.merge,
+                scope: this
+            });
+            return true;
+        }
+        return false;
+    },
+
+    /**
+     * Method: merge
+     * Add all features to the layer.
+     */
+    merge: function(resp) {
+        var features = resp.features;
+        if (features && features.length > 0) {
+            this.layer.addFeatures(features);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Strategy.Fixed"
+});
+/* ======================================================================
     OpenLayers/Tile.js
    ====================================================================== */
 
@@ -7282,39 +13276,7 @@
      * {Boolean} Is the tile loading?
      */
     isLoading: false,
-    
-    /**
-     * Property: isBackBuffer
-     * {Boolean} Is this tile a back buffer tile?
-     */
-    isBackBuffer: false,
-    
-    /**
-     * Property: lastRatio
-     * {Float} Used in transition code only.  This is the previous ratio
-     *     of the back buffer tile resolution to the map resolution.  Compared
-     *     with the current ratio to determine if zooming occurred.
-     */
-    lastRatio: 1,
-
-    /**
-     * Property: isFirstDraw
-     * {Boolean} Is this the first time the tile is being drawn?
-     *     This is used to force resetBackBuffer to synchronize
-     *     the backBufferTile with the foreground tile the first time
-     *     the foreground tile loads so that if the user zooms
-     *     before the layer has fully loaded, the backBufferTile for
-     *     tiles that have been loaded can be used.
-     */
-    isFirstDraw: true,
         
-    /**
-     * Property: backBufferTile
-     * {<OpenLayers.Tile>} A clone of the tile used to create transition
-     *     effects when the tile is moved or changes resolution.
-     */
-    backBufferTile: null,
-        
     /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor.
      *             there is no need for the base tile class to have a url.
      * 
@@ -7360,13 +13322,6 @@
      * Nullify references to prevent circular references and memory leaks.
      */
     destroy:function() {
-        if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, 
-                this.layer.transitionEffect) != -1) {
-            this.layer.events.unregister("loadend", this, this.resetBackBuffer);
-            this.events.unregister('loadend', this, this.resetBackBuffer);            
-        } else {
-            this.events.unregister('loadend', this, this.showTile);
-        }
         this.layer  = null;
         this.bounds = null;
         this.size = null;
@@ -7374,12 +13329,6 @@
         
         this.events.destroy();
         this.events = null;
-        
-        /* clean up the backBufferTile if it exists */
-        if (this.backBufferTile) {
-            this.backBufferTile.destroy();
-            this.backBufferTile = null;
-        }
     },
     
     /**
@@ -7424,50 +13373,12 @@
  
         // The only case where we *wouldn't* want to draw the tile is if the 
         // tile is outside its layer's maxExtent.
-        var drawTile = (withinMaxExtent || this.layer.displayOutsideMaxExtent);
-
-        if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, this.layer.transitionEffect) != -1) {
-            if (drawTile) {
-                //we use a clone of this tile to create a double buffer for visual
-                //continuity.  The backBufferTile is used to create transition
-                //effects while the tile in the grid is repositioned and redrawn
-                if (!this.backBufferTile) {
-                    this.backBufferTile = this.clone();
-                    this.backBufferTile.hide();
-                    // this is important.  It allows the backBuffer to place itself
-                    // appropriately in the DOM.  The Image subclass needs to put
-                    // the backBufferTile behind the main tile so the tiles can
-                    // load over top and display as soon as they are loaded.
-                    this.backBufferTile.isBackBuffer = true;
-                    
-                    // potentially end any transition effects when the tile loads
-                    this.events.register('loadend', this, this.resetBackBuffer);
-                    
-                    // clear transition back buffer tile only after all tiles in
-                    // this layer have loaded to avoid visual glitches
-                    this.layer.events.register("loadend", this, this.resetBackBuffer);
-                }
-                // run any transition effects
-                this.startTransition();
-            } else {
-                // if we aren't going to draw the tile, then the backBuffer should
-                // be hidden too!
-                if (this.backBufferTile) {
-                    this.backBufferTile.clear();
-                }
-            }
-        } else {
-            if (drawTile && this.isFirstDraw) {
-                this.events.register('loadend', this, this.showTile);
-                this.isFirstDraw = false;
-            }   
-        }    
-        this.shouldDraw = drawTile;
-        
+        this.shouldDraw = (withinMaxExtent || this.layer.displayOutsideMaxExtent);
+                
         //clear tile's contents and mark as not drawn
         this.clear();
         
-        return drawTile;
+        return this.shouldDraw;
     },
     
     /** 
@@ -7538,49 +13449,6 @@
                                        topLeft.lat);  
         return bounds;
     },        
-    
-    /** 
-     * Method: startTransition
-     * Prepare the tile for a transition effect.  To be
-     *     implemented by subclasses.
-     */
-    startTransition: function() {},
-    
-    /** 
-     * Method: resetBackBuffer
-     * Triggered by two different events, layer loadend, and tile loadend.
-     *     In any of these cases, we check to see if we can hide the 
-     *     backBufferTile yet and update its parameters to match the 
-     *     foreground tile.
-     *
-     * Basic logic:
-     *  - If the backBufferTile hasn't been drawn yet, reset it
-     *  - If layer is still loading, show foreground tile but don't hide
-     *    the backBufferTile yet
-     *  - If layer is done loading, reset backBuffer tile and show 
-     *    foreground tile
-     */
-    resetBackBuffer: function() {
-        this.showTile();
-        if (this.backBufferTile && 
-            (this.isFirstDraw || !this.layer.numLoadingTiles)) {
-            this.isFirstDraw = false;
-            // check to see if the backBufferTile is within the max extents
-            // before rendering it 
-            var maxExtent = this.layer.maxExtent;
-            var withinMaxExtent = (maxExtent &&
-                                   this.bounds.intersectsBounds(maxExtent, false));
-            if (withinMaxExtent) {
-                this.backBufferTile.position = this.position;
-                this.backBufferTile.bounds = this.bounds;
-                this.backBufferTile.size = this.size;
-                this.backBufferTile.imageSize = this.layer.imageSize || this.size;
-                this.backBufferTile.imageOffset = this.layer.imageOffset;
-                this.backBufferTile.resolution = this.layer.getResolution();
-                this.backBufferTile.renderTile();
-            }
-        }
-    },
         
     /** 
      * Method: showTile
@@ -7607,6 +13475,2440 @@
     CLASS_NAME: "OpenLayers.Tile"
 });
 /* ======================================================================
+    OpenLayers/Ajax.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+OpenLayers.ProxyHost = "";
+//OpenLayers.ProxyHost = "examples/proxy.cgi?url=";
+
+/**
+ * Ajax reader for OpenLayers
+ *
+ *  @uri url to do remote XML http get
+ *  @param {String} 'get' format params (x=y&a=b...)
+ *  @who object to handle callbacks for this request
+ *  @complete  the function to be called on success 
+ *  @failure  the function to be called on failure
+ *  
+ *   example usage from a caller:
+ *  
+ *     caps: function(request) {
+ *      -blah-  
+ *     },
+ *  
+ *     OpenLayers.loadURL(url,params,this,caps);
+ *
+ * Notice the above example does not provide an error handler; a default empty
+ * handler is provided which merely logs the error if a failure handler is not 
+ * supplied
+ *
+ */
+
+
+/**
+ * Function: OpenLayers.nullHandler
+ * @param {} request
+ */
+OpenLayers.nullHandler = function(request) {
+    OpenLayers.Console.userError(OpenLayers.i18n("unhandledRequest", {'statusText':request.statusText}));
+};
+
+/** 
+ * Function: loadURL
+ * Background load a document.  For more flexibility in using XMLHttpRequest,
+ *     see the <OpenLayers.Request> methods.
+ *
+ * Parameters:
+ * uri - {String} URI of source doc
+ * params - {String} or {Object} GET params. Either a string in the form
+ *     "?hello=world&foo=bar" (do not forget the leading question mark)
+ *     or an object in the form {'hello': 'world', 'foo': 'bar}
+ * caller - {Object} object which gets callbacks
+ * onComplete - {Function} Optional callback for success.  The callback
+ *     will be called with this set to caller and will receive the request
+ *     object as an argument.  Note that if you do not specify an onComplete
+ *     function, <OpenLayers.nullHandler> will be called (which pops up a 
+ *     user friendly error message dialog).
+ * onFailure - {Function} Optional callback for failure.  In the event of
+ *     a failure, the callback will be called with this set to caller and will
+ *     receive the request object as an argument.  Note that if you do not
+ *     specify an onComplete function, <OpenLayers.nullHandler> will be called
+ *     (which pops up a user friendly error message dialog).
+ *
+ * Returns:
+ * {<OpenLayers.Request.XMLHttpRequest>}  The request object. To abort loading,
+ *     call request.abort().
+ */
+OpenLayers.loadURL = function(uri, params, caller,
+                                  onComplete, onFailure) {
+    
+    if(typeof params == 'string') {
+        params = OpenLayers.Util.getParameters(params);
+    }
+    var success = (onComplete) ? onComplete : OpenLayers.nullHandler;
+    var failure = (onFailure) ? onFailure : OpenLayers.nullHandler;
+    
+    return OpenLayers.Request.GET({
+        url: uri, params: params,
+        success: success, failure: failure, scope: caller
+    });
+};
+
+/** 
+ * Function: parseXMLString
+ * Parse XML into a doc structure
+ * 
+ * Parameters:
+ * text - {String} 
+ * 
+ * Returns:
+ * {?} Parsed AJAX Responsev
+ */
+OpenLayers.parseXMLString = function(text) {
+
+    //MS sucks, if the server is bad it dies
+    var index = text.indexOf('<');
+    if (index > 0) {
+        text = text.substring(index);
+    }
+
+    var ajaxResponse = OpenLayers.Util.Try(
+        function() {
+            var xmldom = new ActiveXObject('Microsoft.XMLDOM');
+            xmldom.loadXML(text);
+            return xmldom;
+        },
+        function() {
+            return new DOMParser().parseFromString(text, 'text/xml');
+        },
+        function() {
+            var req = new XMLHttpRequest();
+            req.open("GET", "data:" + "text/xml" +
+                     ";charset=utf-8," + encodeURIComponent(text), false);
+            if (req.overrideMimeType) {
+                req.overrideMimeType("text/xml");
+            }
+            req.send(null);
+            return req.responseXML;
+        }
+    );
+
+    return ajaxResponse;
+};
+
+
+/**
+ * Namespace: OpenLayers.Ajax
+ */
+OpenLayers.Ajax = {
+
+    /**
+     * Method: emptyFunction
+     */
+    emptyFunction: function () {},
+
+    /**
+     * Method: getTransport
+     * 
+     * Returns: 
+     * {Object} Transport mechanism for whichever browser we're in, or false if
+     *          none available.
+     */
+    getTransport: function() {
+        return OpenLayers.Util.Try(
+            function() {return new XMLHttpRequest();},
+            function() {return new ActiveXObject('Msxml2.XMLHTTP');},
+            function() {return new ActiveXObject('Microsoft.XMLHTTP');}
+        ) || false;
+    },
+
+    /**
+     * Property: activeRequestCount
+     * {Integer}
+     */
+    activeRequestCount: 0
+};
+
+/**
+ * Namespace: OpenLayers.Ajax.Responders
+ * {Object}
+ */
+OpenLayers.Ajax.Responders = {
+  
+    /**
+     * Property: responders
+     * {Array}
+     */
+    responders: [],
+
+    /**
+     * Method: register
+     *  
+     * Parameters:
+     * responderToAdd - {?}
+     */
+    register: function(responderToAdd) {
+        for (var i = 0; i < this.responders.length; i++){
+            if (responderToAdd == this.responders[i]){
+                return;
+            }
+        }
+        this.responders.push(responderToAdd);
+    },
+
+    /**
+     * Method: unregister
+     *  
+     * Parameters:
+     * responderToRemove - {?}
+     */
+    unregister: function(responderToRemove) {
+        OpenLayers.Util.removeItem(this.reponders, responderToRemove);
+    },
+
+    /**
+     * Method: dispatch
+     * 
+     * Parameters:
+     * callback - {?}
+     * request - {?}
+     * transport - {?}
+     */
+    dispatch: function(callback, request, transport) {
+        var responder;
+        for (var i = 0; i < this.responders.length; i++) {
+            responder = this.responders[i];
+     
+            if (responder[callback] && 
+                typeof responder[callback] == 'function') {
+                try {
+                    responder[callback].apply(responder, 
+                                              [request, transport]);
+                } catch (e) {}
+            }
+        }
+    }
+};
+
+OpenLayers.Ajax.Responders.register({
+    /** 
+     * Function: onCreate
+     */
+    onCreate: function() {
+        OpenLayers.Ajax.activeRequestCount++;
+    },
+
+    /**
+     * Function: onComplete
+     */
+     onComplete: function() {
+         OpenLayers.Ajax.activeRequestCount--;
+     }
+});
+
+/**
+ * Class: OpenLayers.Ajax.Base
+ */
+OpenLayers.Ajax.Base = OpenLayers.Class({
+      
+    /**
+     * Constructor: OpenLayers.Ajax.Base
+     * 
+     * Parameters: 
+     * options - {Object}
+     */
+    initialize: function(options) {
+        this.options = {
+            method:       'post',
+            asynchronous: true,
+            contentType:  'application/xml',
+            parameters:   ''
+        };
+        OpenLayers.Util.extend(this.options, options || {});
+        
+        this.options.method = this.options.method.toLowerCase();
+        
+        if (typeof this.options.parameters == 'string') {
+            this.options.parameters = 
+                OpenLayers.Util.getParameters(this.options.parameters);
+        }
+    }
+});
+
+/**
+ * Class: OpenLayers.Ajax.Request
+ * *Deprecated*.  Use <OpenLayers.Request> method instead.
+ *
+ * Inherit:
+ *  - <OpenLayers.Ajax.Base>
+ */
+OpenLayers.Ajax.Request = OpenLayers.Class(OpenLayers.Ajax.Base, {
+
+    /**
+     * Property: _complete
+     *
+     * {Boolean}
+     */
+    _complete: false,
+      
+    /**
+     * Constructor: OpenLayers.Ajax.Request
+     * 
+     * Parameters: 
+     * url - {String}
+     * options - {Object}
+     */
+    initialize: function(url, options) {
+        OpenLayers.Ajax.Base.prototype.initialize.apply(this, [options]);
+        
+        if (OpenLayers.ProxyHost && OpenLayers.String.startsWith(url, "http")) {
+            url = OpenLayers.ProxyHost + encodeURIComponent(url);
+        }
+        
+        this.transport = OpenLayers.Ajax.getTransport();
+        this.request(url);
+    },
+
+    /**
+     * Method: request
+     * 
+     * Parameters:
+     * url - {String}
+     */
+    request: function(url) {
+        this.url = url;
+        this.method = this.options.method;
+        var params = OpenLayers.Util.extend({}, this.options.parameters);
+        
+        if (this.method != 'get' && this.method != 'post') {
+            // simulate other verbs over post
+            params['_method'] = this.method;
+            this.method = 'post';
+        }
+
+        this.parameters = params;        
+        
+        if (params = OpenLayers.Util.getParameterString(params)) {
+            // when GET, append parameters to URL
+            if (this.method == 'get') {
+                this.url += ((this.url.indexOf('?') > -1) ? '&' : '?') + params;
+            } else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+                params += '&_=';
+            }
+        }
+        try {
+            var response = new OpenLayers.Ajax.Response(this);
+            if (this.options.onCreate) {
+                this.options.onCreate(response);
+            }
+            
+            OpenLayers.Ajax.Responders.dispatch('onCreate', 
+                                                this, 
+                                                response);
+    
+            this.transport.open(this.method.toUpperCase(), 
+                                this.url,
+                                this.options.asynchronous);
+    
+            if (this.options.asynchronous) {
+                window.setTimeout(
+                    OpenLayers.Function.bind(this.respondToReadyState, this, 1),
+                    10);
+            }
+            
+            this.transport.onreadystatechange = 
+                OpenLayers.Function.bind(this.onStateChange, this);    
+            this.setRequestHeaders();
+    
+            this.body =  this.method == 'post' ?
+                (this.options.postBody || params) : null;
+            this.transport.send(this.body);
+    
+            // Force Firefox to handle ready state 4 for synchronous requests
+            if (!this.options.asynchronous && 
+                this.transport.overrideMimeType) {
+                this.onStateChange();
+            }
+        } catch (e) {
+            this.dispatchException(e);
+        }
+    },
+
+    /**
+     * Method: onStateChange
+     */
+    onStateChange: function() {
+        var readyState = this.transport.readyState;
+        if (readyState > 1 && !((readyState == 4) && this._complete)) {
+            this.respondToReadyState(this.transport.readyState);
+        }
+    },
+     
+    /**
+     * Method: setRequestHeaders
+     */
+    setRequestHeaders: function() {
+        var headers = {
+            'X-Requested-With': 'XMLHttpRequest',
+            'Accept': 'text/javascript, text/html, application/xml, text/xml, */*',
+            'OpenLayers': true
+        };
+
+        if (this.method == 'post') {
+            headers['Content-type'] = this.options.contentType +
+                (this.options.encoding ? '; charset=' + this.options.encoding : '');
+    
+            /* Force "Connection: close" for older Mozilla browsers to work
+             * around a bug where XMLHttpRequest sends an incorrect
+             * Content-length header. See Mozilla Bugzilla #246651.
+             */
+            if (this.transport.overrideMimeType &&
+                (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) {
+                headers['Connection'] = 'close';
+            }
+        }
+        // user-defined headers
+        if (typeof this.options.requestHeaders == 'object') {    
+            var extras = this.options.requestHeaders;
+            
+            if (typeof extras.push == 'function') {
+                for (var i = 0, length = extras.length; i < length; i += 2) {
+                    headers[extras[i]] = extras[i+1];
+                }
+            } else {
+                for (var i in extras) {
+                    headers[i] = extras[i];
+                }
+            }
+        }
+        
+        for (var name in headers) {
+            this.transport.setRequestHeader(name, headers[name]);
+        }
+    },
+    
+    /**
+     * Method: success
+     *
+     * Returns:
+     * {Boolean} - 
+     */
+    success: function() {
+        var status = this.getStatus();
+        return !status || (status >=200 && status < 300);
+    },
+    
+    /**
+     * Method: getStatus
+     *
+     * Returns:
+     * {Integer} - Status
+     */
+    getStatus: function() {
+        try {
+            return this.transport.status || 0;
+        } catch (e) {
+            return 0;
+        }
+    },
+
+    /**
+     * Method: respondToReadyState
+     *
+     * Parameters:
+     * readyState - {?}
+     */
+    respondToReadyState: function(readyState) {
+        var state = OpenLayers.Ajax.Request.Events[readyState];
+        var response = new OpenLayers.Ajax.Response(this);
+    
+        if (state == 'Complete') {
+            try {
+                this._complete = true;
+                (this.options['on' + response.status] ||
+                    this.options['on' + (this.success() ? 'Success' : 'Failure')] ||
+                    OpenLayers.Ajax.emptyFunction)(response);
+            } catch (e) {
+                this.dispatchException(e);
+            }
+    
+            var contentType = response.getHeader('Content-type');
+        }
+    
+        try {
+            (this.options['on' + state] || 
+             OpenLayers.Ajax.emptyFunction)(response);
+             OpenLayers.Ajax.Responders.dispatch('on' + state, 
+                                                 this, 
+                                                 response);
+        } catch (e) {
+            this.dispatchException(e);
+        }
+    
+        if (state == 'Complete') {
+            // avoid memory leak in MSIE: clean up
+            this.transport.onreadystatechange = OpenLayers.Ajax.emptyFunction;
+        }
+    },
+    
+    /**
+     * Method: getHeader
+     * 
+     * Parameters:
+     * name - {String} Header name
+     *
+     * Returns:
+     * {?} - response header for the given name
+     */
+    getHeader: function(name) {
+        try {
+            return this.transport.getResponseHeader(name);
+        } catch (e) {
+            return null;
+        }
+    },
+
+    /**
+     * Method: dispatchException
+     * If the optional onException function is set, execute it
+     * and then dispatch the call to any other listener registered
+     * for onException.
+     * 
+     * If no optional onException function is set, we suspect that
+     * the user may have also not used
+     * OpenLayers.Ajax.Responders.register to register a listener
+     * for the onException call.  To make sure that something
+     * gets done with this exception, only dispatch the call if there
+     * are listeners.
+     *
+     * If you explicitly want to swallow exceptions, set
+     * request.options.onException to an empty function (function(){})
+     * or register an empty function with <OpenLayers.Ajax.Responders>
+     * for onException.
+     * 
+     * Parameters:
+     * exception - {?}
+     */
+    dispatchException: function(exception) {
+        var handler = this.options.onException;
+        if(handler) {
+            // call options.onException and alert any other listeners
+            handler(this, exception);
+            OpenLayers.Ajax.Responders.dispatch('onException', this, exception);
+        } else {
+            // check if there are any other listeners
+            var listener = false;
+            var responders = OpenLayers.Ajax.Responders.responders;
+            for (var i = 0; i < responders.length; i++) {
+                if(responders[i].onException) {
+                    listener = true;
+                    break;
+                }
+            }
+            if(listener) {
+                // call all listeners
+                OpenLayers.Ajax.Responders.dispatch('onException', this, exception);
+            } else {
+                // let the exception through
+                throw exception;
+            }
+        }
+    }
+});
+
+/** 
+ * Property: Events
+ * {Array(String)}
+ */
+OpenLayers.Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+/**
+ * Class: OpenLayers.Ajax.Response
+ */
+OpenLayers.Ajax.Response = OpenLayers.Class({
+
+    /**
+     * Property: status
+     *
+     * {Integer}
+     */
+    status: 0,
+    
+
+    /**
+     * Property: statusText
+     *
+     * {String}
+     */
+    statusText: '',
+      
+    /**
+     * Constructor: OpenLayers.Ajax.Response
+     * 
+     * Parameters: 
+     * request - {Object}
+     */
+    initialize: function(request) {
+        this.request = request;
+        var transport = this.transport = request.transport,
+            readyState = this.readyState = transport.readyState;
+        
+        if ((readyState > 2 &&
+            !(!!(window.attachEvent && !window.opera))) ||
+            readyState == 4) {
+            this.status       = this.getStatus();
+            this.statusText   = this.getStatusText();
+            this.responseText = transport.responseText == null ?
+                '' : String(transport.responseText);
+        }
+        
+        if(readyState == 4) {
+            var xml = transport.responseXML;
+            this.responseXML  = xml === undefined ? null : xml;
+        }
+    },
+    
+    /**
+     * Method: getStatus
+     */
+    getStatus: OpenLayers.Ajax.Request.prototype.getStatus,
+    
+    /**
+     * Method: getStatustext
+     *
+     * Returns:
+     * {String} - statusText
+     */
+    getStatusText: function() {
+        try {
+            return this.transport.statusText || '';
+        } catch (e) {
+            return '';
+        }
+    },
+    
+    /**
+     * Method: getHeader
+     */
+    getHeader: OpenLayers.Ajax.Request.prototype.getHeader,
+    
+    /** 
+     * Method: getResponseHeader
+     *
+     * Returns:
+     * {?} - response header for given name
+     */
+    getResponseHeader: function(name) {
+        return this.transport.getResponseHeader(name);
+    }
+});
+
+
+/**
+ * Function: getElementsByTagNameNS
+ * 
+ * Parameters:
+ * parentnode - {?}
+ * nsuri - {?}
+ * nsprefix - {?}
+ * tagname - {?}
+ * 
+ * Returns:
+ * {?}
+ */
+OpenLayers.Ajax.getElementsByTagNameNS  = function(parentnode, nsuri, 
+                                                   nsprefix, tagname) {
+    var elem = null;
+    if (parentnode.getElementsByTagNameNS) {
+        elem = parentnode.getElementsByTagNameNS(nsuri, tagname);
+    } else {
+        elem = parentnode.getElementsByTagName(nsprefix + ':' + tagname);
+    }
+    return elem;
+};
+
+
+/**
+ * Function: serializeXMLToString
+ * Wrapper function around XMLSerializer, which doesn't exist/work in
+ *     IE/Safari. We need to come up with a way to serialize in those browser:
+ *     for now, these browsers will just fail. #535, #536
+ *
+ * Parameters: 
+ * xmldom {XMLNode} xml dom to serialize
+ * 
+ * Returns:
+ * {?}
+ */
+OpenLayers.Ajax.serializeXMLToString = function(xmldom) {
+    var serializer = new XMLSerializer();
+    var data = serializer.serializeToString(xmldom);
+    return data;
+};
+/* ======================================================================
+    OpenLayers/Control/MouseToolbar.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Control/MouseDefaults.js
+ */
+
+/**
+ * Class: OpenLayers.Control.MouseToolbar
+ * This class is DEPRECATED in 2.4 and will be removed by 3.0.
+ * If you need this functionality, use Control.NavToolbar instead!!! 
+ */
+OpenLayers.Control.MouseToolbar = OpenLayers.Class(
+                                            OpenLayers.Control.MouseDefaults, {
+    
+    /**
+     * Property: mode
+     */ 
+    mode: null,
+    /**
+     * Property: buttons
+     */
+    buttons: null,
+    
+    /**
+     * APIProperty: direction
+     * {String} 'vertical' or 'horizontal'
+     */
+    direction: "vertical",
+    
+    /**
+     * Property: buttonClicked
+     * {String}
+     */
+    buttonClicked: null,
+    
+    /**
+     * Constructor: OpenLayers.Control.MouseToolbar
+     *
+     * Parameters:
+     * position - {<OpenLayers.Pixel>}
+     * direction - {String}
+     */
+    initialize: function(position, direction) {
+        OpenLayers.Control.prototype.initialize.apply(this, arguments);
+        this.position = new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,
+                                             OpenLayers.Control.MouseToolbar.Y);
+        if (position) {
+            this.position = position;
+        }
+        if (direction) {
+            this.direction = direction; 
+        }
+        this.measureDivs = [];
+    },
+    
+    /**
+     * APIMethod: destroy 
+     */
+    destroy: function() {
+        for( var btnId in this.buttons) {
+            var btn = this.buttons[btnId];
+            btn.map = null;
+            btn.events.destroy();
+        }
+        OpenLayers.Control.MouseDefaults.prototype.destroy.apply(this, 
+                                                                 arguments);
+    },
+    
+    /**
+     * Method: draw
+     */
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments); 
+        OpenLayers.Control.MouseDefaults.prototype.draw.apply(this, arguments);
+        this.buttons = {};
+        var sz = new OpenLayers.Size(28,28);
+        var centered = new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,0);
+        this._addButton("zoombox", "drag-rectangle-off.png", "drag-rectangle-on.png", centered, sz, "Shift->Drag to zoom to area");
+        centered = centered.add((this.direction == "vertical" ? 0 : sz.w), (this.direction == "vertical" ? sz.h : 0));
+        this._addButton("pan", "panning-hand-off.png", "panning-hand-on.png", centered, sz, "Drag the map to pan.");
+        centered = centered.add((this.direction == "vertical" ? 0 : sz.w), (this.direction == "vertical" ? sz.h : 0));
+        this.switchModeTo("pan");
+
+        return this.div;
+    },
+    
+    /**
+     * Method: _addButton
+     */
+    _addButton:function(id, img, activeImg, xy, sz, title) {
+        var imgLocation = OpenLayers.Util.getImagesLocation() + img;
+        var activeImgLocation = OpenLayers.Util.getImagesLocation() + activeImg;
+        // var btn = new ol.AlphaImage("_"+id, imgLocation, xy, sz);
+        var btn = OpenLayers.Util.createAlphaImageDiv(
+                                    "OpenLayers_Control_MouseToolbar_" + id, 
+                                    xy, sz, imgLocation, "absolute");
+
+        //we want to add the outer div
+        this.div.appendChild(btn);
+        btn.imgLocation = imgLocation;
+        btn.activeImgLocation = activeImgLocation;
+        
+        btn.events = new OpenLayers.Events(this, btn, null, true);
+        btn.events.on({
+            "mousedown": this.buttonDown,
+            "mouseup": this.buttonUp,
+            "dblclick": OpenLayers.Event.stop,
+            scope: this
+        });
+        btn.action = id;
+        btn.title = title;
+        btn.alt = title;
+        btn.map = this.map;
+
+        //we want to remember/reference the outer div
+        this.buttons[id] = btn;
+        return btn;
+    },
+
+    /**
+     * Method: buttonDown
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    buttonDown: function(evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        this.buttonClicked = evt.element.action;
+        OpenLayers.Event.stop(evt);
+    },
+
+    /**
+     * Method: buttonUp
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    buttonUp: function(evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        if (this.buttonClicked != null) {
+            if (this.buttonClicked == evt.element.action) {
+                this.switchModeTo(evt.element.action);
+            }
+            OpenLayers.Event.stop(evt);
+            this.buttonClicked = null;
+        }
+    },
+    
+    /**
+     * Method: defaultDblClick 
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultDblClick: function (evt) {
+        this.switchModeTo("pan");
+        this.performedDrag = false;
+        var newCenter = this.map.getLonLatFromViewPortPx( evt.xy ); 
+        this.map.setCenter(newCenter, this.map.zoom + 1);
+        OpenLayers.Event.stop(evt);
+        return false;
+    },
+
+    /**
+     * Method: defaultMouseDown
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseDown: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        this.mouseDragStart = evt.xy.clone();
+        this.performedDrag = false;
+        this.startViaKeyboard = false;
+        if (evt.shiftKey && this.mode !="zoombox") {
+            this.switchModeTo("zoombox");
+            this.startViaKeyboard = true;
+        } else if (evt.altKey && this.mode !="measure") {
+            this.switchModeTo("measure");
+        } else if (!this.mode) {
+            this.switchModeTo("pan");
+        }
+        
+        switch (this.mode) {
+            case "zoombox":
+                this.map.div.style.cursor = "crosshair";
+                this.zoomBox = OpenLayers.Util.createDiv('zoomBox',
+                                                         this.mouseDragStart,
+                                                         null,
+                                                         null,
+                                                         "absolute",
+                                                         "2px solid red");
+                this.zoomBox.style.backgroundColor = "white";
+                this.zoomBox.style.filter = "alpha(opacity=50)"; // IE
+                this.zoomBox.style.opacity = "0.50";
+                this.zoomBox.style.fontSize = "1px";
+                this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+                this.map.viewPortDiv.appendChild(this.zoomBox);
+                this.performedDrag = true;
+                break;
+            case "measure":
+                var distance = "";
+                if (this.measureStart) {
+                    var measureEnd = this.map.getLonLatFromViewPortPx(this.mouseDragStart);
+                    distance = OpenLayers.Util.distVincenty(this.measureStart, measureEnd);
+                    distance = Math.round(distance * 100) / 100;
+                    distance = distance + "km";
+                    this.measureStartBox = this.measureBox;
+                }    
+                this.measureStart = this.map.getLonLatFromViewPortPx(this.mouseDragStart);;
+                this.measureBox = OpenLayers.Util.createDiv(null,
+                                                         this.mouseDragStart.add(
+                                                           -2-parseInt(this.map.layerContainerDiv.style.left),
+                                                           -2-parseInt(this.map.layerContainerDiv.style.top)),
+                                                         null,
+                                                         null,
+                                                         "absolute");
+                this.measureBox.style.width="4px";
+                this.measureBox.style.height="4px";
+                this.measureBox.style.fontSize = "1px";
+                this.measureBox.style.backgroundColor="red";
+                this.measureBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+                this.map.layerContainerDiv.appendChild(this.measureBox);
+                if (distance) {
+                    this.measureBoxDistance = OpenLayers.Util.createDiv(null,
+                                                         this.mouseDragStart.add(
+                                                           -2-parseInt(this.map.layerContainerDiv.style.left),
+                                                           2-parseInt(this.map.layerContainerDiv.style.top)),
+                                                         null,
+                                                         null,
+                                                         "absolute");
+                    
+                    this.measureBoxDistance.innerHTML = distance;
+                    this.measureBoxDistance.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+                    this.map.layerContainerDiv.appendChild(this.measureBoxDistance);
+                    this.measureDivs.push(this.measureBoxDistance);
+                }
+                this.measureBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+                this.map.layerContainerDiv.appendChild(this.measureBox);
+                this.measureDivs.push(this.measureBox);
+                break;
+            default:
+                this.map.div.style.cursor = "move";
+                break;
+        }
+        document.onselectstart = function() { return false; };
+        OpenLayers.Event.stop(evt);
+    },
+
+    /**
+     * Method: switchModeTo 
+     *
+     * Parameters:
+     * mode - {String} 
+     */
+    switchModeTo: function(mode) {
+        if (mode != this.mode) {
+            
+
+            if (this.mode && this.buttons[this.mode]) {
+                OpenLayers.Util.modifyAlphaImageDiv(this.buttons[this.mode], null, null, null, this.buttons[this.mode].imgLocation);
+            }
+            if (this.mode == "measure" && mode != "measure") {
+                for(var i=0, len=this.measureDivs.length; i<len; i++) {
+                    if (this.measureDivs[i]) { 
+                        this.map.layerContainerDiv.removeChild(this.measureDivs[i]);
+                    }
+                }
+                this.measureDivs = [];
+                this.measureStart = null;
+            }
+            this.mode = mode;
+            if (this.buttons[mode]) {
+                OpenLayers.Util.modifyAlphaImageDiv(this.buttons[mode], null, null, null, this.buttons[mode].activeImgLocation);
+            }
+            switch (this.mode) {
+                case "zoombox":
+                    this.map.div.style.cursor = "crosshair";
+                    break;
+                default:
+                    this.map.div.style.cursor = "";
+                    break;
+            }
+
+        } 
+    }, 
+
+    /**
+     * Method: leaveMode
+     */
+    leaveMode: function() {
+        this.switchModeTo("pan");
+    },
+    
+    /**
+     * Method: defaultMouseMove
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseMove: function (evt) {
+        if (this.mouseDragStart != null) {
+            switch (this.mode) {
+                case "zoombox": 
+                    var deltaX = Math.abs(this.mouseDragStart.x - evt.xy.x);
+                    var deltaY = Math.abs(this.mouseDragStart.y - evt.xy.y);
+                    this.zoomBox.style.width = Math.max(1, deltaX) + "px";
+                    this.zoomBox.style.height = Math.max(1, deltaY) + "px";
+                    if (evt.xy.x < this.mouseDragStart.x) {
+                        this.zoomBox.style.left = evt.xy.x+"px";
+                    }
+                    if (evt.xy.y < this.mouseDragStart.y) {
+                        this.zoomBox.style.top = evt.xy.y+"px";
+                    }
+                    break;
+                default:
+                    var deltaX = this.mouseDragStart.x - evt.xy.x;
+                    var deltaY = this.mouseDragStart.y - evt.xy.y;
+                    var size = this.map.getSize();
+                    var newXY = new OpenLayers.Pixel(size.w / 2 + deltaX,
+                                                     size.h / 2 + deltaY);
+                    var newCenter = this.map.getLonLatFromViewPortPx( newXY ); 
+                    this.map.setCenter(newCenter, null, true);
+                    this.mouseDragStart = evt.xy.clone();
+            }
+            this.performedDrag = true;
+        }
+    },
+
+    /**
+     * Method: defaultMouseUp
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseUp: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        switch (this.mode) {
+            case "zoombox":
+                this.zoomBoxEnd(evt);
+                if (this.startViaKeyboard) {
+                    this.leaveMode();
+                }
+                break;
+            case "pan":
+                if (this.performedDrag) {
+                    this.map.setCenter(this.map.center);
+                }        
+        }
+        document.onselectstart = null;
+        this.mouseDragStart = null;
+        this.map.div.style.cursor = "default";
+    },
+
+    /**
+     * Method: defaultMouseOut
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseOut: function (evt) {
+        if (this.mouseDragStart != null
+            && OpenLayers.Util.mouseLeft(evt, this.map.div)) {
+            if (this.zoomBox) {
+                this.removeZoomBox();
+                if (this.startViaKeyboard) {
+                    this.leaveMode();
+                }
+            }
+            this.mouseDragStart = null;
+            this.map.div.style.cursor = "default";
+        }
+    },
+
+    /**
+     * Method: defaultClick
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultClick: function (evt) {
+        if (this.performedDrag)  {
+            this.performedDrag = false;
+            return false;
+        }
+    },
+    
+    CLASS_NAME: "OpenLayers.Control.MouseToolbar"
+});
+
+OpenLayers.Control.MouseToolbar.X = 6;
+OpenLayers.Control.MouseToolbar.Y = 300;
+/* ======================================================================
+    OpenLayers/Control/PanZoomBar.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control/PanZoom.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PanZoomBar
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control.PanZoom>
+ */
+OpenLayers.Control.PanZoomBar = OpenLayers.Class(OpenLayers.Control.PanZoom, {
+
+    /** 
+     * APIProperty: zoomStopWidth
+     */
+    zoomStopWidth: 18,
+
+    /** 
+     * APIProperty: zoomStopHeight
+     */
+    zoomStopHeight: 11,
+
+    /** 
+     * Property: slider
+     */
+    slider: null,
+
+    /** 
+     * Property: sliderEvents
+     * {<OpenLayers.Events>}
+     */
+    sliderEvents: null,
+
+    /** 
+     * Property: zoomBarDiv
+     * {DOMElement}
+     */
+    zoomBarDiv: null,
+
+    /** 
+     * Property: divEvents
+     * {<OpenLayers.Events>}
+     */
+    divEvents: null,
+
+    /** 
+     * Property: zoomWorldIcon
+     * {Boolean}
+     */
+    zoomWorldIcon: false,
+
+    /**
+     * Constructor: OpenLayers.Control.PanZoomBar
+     */ 
+    initialize: function() {
+        OpenLayers.Control.PanZoom.prototype.initialize.apply(this, arguments);
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+
+        this.div.removeChild(this.slider);
+        this.slider = null;
+
+        this.sliderEvents.destroy();
+        this.sliderEvents = null;
+        
+        this.div.removeChild(this.zoombarDiv);
+        this.zoomBarDiv = null;
+
+        this.divEvents.destroy();
+        this.divEvents = null;
+
+        this.map.events.un({
+            "zoomend": this.moveZoomBar,
+            "changebaselayer": this.redraw,
+            scope: this
+        });
+
+        OpenLayers.Control.PanZoom.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * Method: setMap
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        OpenLayers.Control.PanZoom.prototype.setMap.apply(this, arguments);
+        this.map.events.register("changebaselayer", this, this.redraw);
+    },
+
+    /** 
+     * Method: redraw
+     * clear the div and start over.
+     */
+    redraw: function() {
+        if (this.div != null) {
+            this.div.innerHTML = "";
+        }  
+        this.draw();
+    },
+    
+    /**
+    * Method: draw 
+    *
+    * Parameters:
+    * px - {<OpenLayers.Pixel>} 
+    */
+    draw: function(px) {
+        // initialize our internal div
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        px = this.position.clone();
+
+        // place the controls
+        this.buttons = [];
+
+        var sz = new OpenLayers.Size(18,18);
+        var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y);
+        var wposition = sz.w;
+
+        if (this.zoomWorldIcon) {
+            centered = new OpenLayers.Pixel(px.x+sz.w, px.y);
+        }
+
+        this._addButton("panup", "north-mini.png", centered, sz);
+        px.y = centered.y+sz.h;
+        this._addButton("panleft", "west-mini.png", px, sz);
+        if (this.zoomWorldIcon) {
+            this._addButton("zoomworld", "zoom-world-mini.png", px.add(sz.w, 0), sz);
+            
+            wposition *= 2;
+        }
+        this._addButton("panright", "east-mini.png", px.add(wposition, 0), sz);
+        this._addButton("pandown", "south-mini.png", centered.add(0, sz.h*2), sz);
+        this._addButton("zoomin", "zoom-plus-mini.png", centered.add(0, sz.h*3+5), sz);
+        centered = this._addZoomBar(centered.add(0, sz.h*4 + 5));
+        this._addButton("zoomout", "zoom-minus-mini.png", centered, sz);
+        return this.div;
+    },
+
+    /** 
+    * Method: _addZoomBar
+    * 
+    * Parameters:
+    * location - {<OpenLayers.Pixel>} where zoombar drawing is to start.
+    */
+    _addZoomBar:function(centered) {
+        var imgLocation = OpenLayers.Util.getImagesLocation();
+        
+        var id = this.id + "_" + this.map.id;
+        var zoomsToEnd = this.map.getNumZoomLevels() - 1 - this.map.getZoom();
+        var slider = OpenLayers.Util.createAlphaImageDiv(id,
+                       centered.add(-1, zoomsToEnd * this.zoomStopHeight), 
+                       new OpenLayers.Size(20,9), 
+                       imgLocation+"slider.png",
+                       "absolute");
+        this.slider = slider;
+        
+        this.sliderEvents = new OpenLayers.Events(this, slider, null, true,
+                                            {includeXY: true});
+        this.sliderEvents.on({
+            "mousedown": this.zoomBarDown,
+            "mousemove": this.zoomBarDrag,
+            "mouseup": this.zoomBarUp,
+            "dblclick": this.doubleClick,
+            "click": this.doubleClick
+        });
+        
+        var sz = new OpenLayers.Size();
+        sz.h = this.zoomStopHeight * this.map.getNumZoomLevels();
+        sz.w = this.zoomStopWidth;
+        var div = null;
+        
+        if (OpenLayers.Util.alphaHack()) {
+            var id = this.id + "_" + this.map.id;
+            div = OpenLayers.Util.createAlphaImageDiv(id, centered,
+                                      new OpenLayers.Size(sz.w, 
+                                              this.zoomStopHeight),
+                                      imgLocation + "zoombar.png", 
+                                      "absolute", null, "crop");
+            div.style.height = sz.h + "px";
+        } else {
+            div = OpenLayers.Util.createDiv(
+                        'OpenLayers_Control_PanZoomBar_Zoombar' + this.map.id,
+                        centered,
+                        sz,
+                        imgLocation+"zoombar.png");
+        }
+        
+        this.zoombarDiv = div;
+        
+        this.divEvents = new OpenLayers.Events(this, div, null, true, 
+                                                {includeXY: true});
+        this.divEvents.on({
+            "mousedown": this.divClick,
+            "mousemove": this.passEventToSlider,
+            "dblclick": this.doubleClick,
+            "click": this.doubleClick
+        });
+        
+        this.div.appendChild(div);
+
+        this.startTop = parseInt(div.style.top);
+        this.div.appendChild(slider);
+
+        this.map.events.register("zoomend", this, this.moveZoomBar);
+
+        centered = centered.add(0, 
+            this.zoomStopHeight * this.map.getNumZoomLevels());
+        return centered; 
+    },
+    
+    /*
+     * Method: passEventToSlider
+     * This function is used to pass events that happen on the div, or the map,
+     * through to the slider, which then does its moving thing.
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     */
+    passEventToSlider:function(evt) {
+        this.sliderEvents.handleBrowserEvent(evt);
+    },
+    
+    /*
+     * Method: divClick
+     * Picks up on clicks directly on the zoombar div
+     *           and sets the zoom level appropriately.
+     */
+    divClick: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        var y = evt.xy.y;
+        var top = OpenLayers.Util.pagePosition(evt.object)[1];
+        var levels = (y - top)/this.zoomStopHeight;
+        if(!this.map.fractionalZoom) {
+            levels = Math.floor(levels);
+        }    
+        var zoom = (this.map.getNumZoomLevels() - 1) - levels; 
+        zoom = Math.min(Math.max(zoom, 0), this.map.getNumZoomLevels() - 1);
+        this.map.zoomTo(zoom);
+        OpenLayers.Event.stop(evt);
+    },
+    
+    /*
+     * Method: zoomBarDown
+     * event listener for clicks on the slider
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     */
+    zoomBarDown:function(evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        this.map.events.on({
+            "mousemove": this.passEventToSlider,
+            "mouseup": this.passEventToSlider,
+            scope: this
+        });
+        this.mouseDragStart = evt.xy.clone();
+        this.zoomStart = evt.xy.clone();
+        this.div.style.cursor = "move";
+        // reset the div offsets just in case the div moved
+        this.zoombarDiv.offsets = null; 
+        OpenLayers.Event.stop(evt);
+    },
+    
+    /*
+     * Method: zoomBarDrag
+     * This is what happens when a click has occurred, and the client is
+     * dragging.  Here we must ensure that the slider doesn't go beyond the
+     * bottom/top of the zoombar div, as well as moving the slider to its new
+     * visual location
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     */
+    zoomBarDrag:function(evt) {
+        if (this.mouseDragStart != null) {
+            var deltaY = this.mouseDragStart.y - evt.xy.y;
+            var offsets = OpenLayers.Util.pagePosition(this.zoombarDiv);
+            if ((evt.clientY - offsets[1]) > 0 && 
+                (evt.clientY - offsets[1]) < parseInt(this.zoombarDiv.style.height) - 2) {
+                var newTop = parseInt(this.slider.style.top) - deltaY;
+                this.slider.style.top = newTop+"px";
+            }
+            this.mouseDragStart = evt.xy.clone();
+            OpenLayers.Event.stop(evt);
+        }
+    },
+    
+    /*
+     * Method: zoomBarUp
+     * Perform cleanup when a mouseup event is received -- discover new zoom
+     * level and switch to it.
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     */
+    zoomBarUp:function(evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        if (this.zoomStart) {
+            this.div.style.cursor="";
+            this.map.events.un({
+                "mouseup": this.passEventToSlider,
+                "mousemove": this.passEventToSlider,
+                scope: this
+            });
+            var deltaY = this.zoomStart.y - evt.xy.y;
+            var zoomLevel = this.map.zoom;
+            if (this.map.fractionalZoom) {
+                zoomLevel += deltaY/this.zoomStopHeight;
+                zoomLevel = Math.min(Math.max(zoomLevel, 0), 
+                                     this.map.getNumZoomLevels() - 1);
+            } else {
+                zoomLevel += Math.round(deltaY/this.zoomStopHeight);
+            }
+            this.map.zoomTo(zoomLevel);
+            this.moveZoomBar();
+            this.mouseDragStart = null;
+            OpenLayers.Event.stop(evt);
+        }
+    },
+    
+    /*
+    * Method: moveZoomBar
+    * Change the location of the slider to match the current zoom level.
+    */
+    moveZoomBar:function() {
+        var newTop = 
+            ((this.map.getNumZoomLevels()-1) - this.map.getZoom()) * 
+            this.zoomStopHeight + this.startTop + 1;
+        this.slider.style.top = newTop + "px";
+    },    
+    
+    CLASS_NAME: "OpenLayers.Control.PanZoomBar"
+});
+/* ======================================================================
+    OpenLayers/Control/Permalink.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Control/ArgParser.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Permalink
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.Permalink = OpenLayers.Class(OpenLayers.Control, {
+    
+    /**
+     * APIProperty: argParserClass
+     * {Class} The ArgParser control class (not instance) to use with this
+     *     control.
+     */
+    argParserClass: OpenLayers.Control.ArgParser,
+
+    /** 
+     * Property: element 
+     * {DOMElement}
+     */
+    element: null,
+    
+    /** 
+     * APIProperty: base
+     * {String}
+     */
+    base: '',
+
+    /** 
+     * APIProperty: displayProjection
+     * {<OpenLayers.Projection>} Requires proj4js support.  Projection used
+     *     when creating the coordinates in the link. This will reproject the
+     *     map coordinates into display coordinates. If you are using this
+     *     functionality, the permalink which is last added to the map will
+     *     determine the coordinate type which is read from the URL, which
+     *     means you should not add permalinks with different
+     *     displayProjections to the same map. 
+     */
+    displayProjection: null, 
+
+    /**
+     * Constructor: OpenLayers.Control.Permalink
+     *
+     * Parameters: 
+     * element - {DOMElement} 
+     * base - {String} 
+     * options - {Object} options to the control. 
+     */
+    initialize: function(element, base, options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.element = OpenLayers.Util.getElement(element);        
+        this.base = base || document.location.href;
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function()  {
+        if (this.element.parentNode == this.div) {
+            this.div.removeChild(this.element);
+        }
+        this.element = null;
+
+        this.map.events.unregister('moveend', this, this.updateLink);
+
+        OpenLayers.Control.prototype.destroy.apply(this, arguments); 
+    },
+
+    /**
+     * Method: setMap
+     * Set the map property for the control. 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+        //make sure we have an arg parser attached
+        for(var i=0, len=this.map.controls.length; i<len; i++) {
+            var control = this.map.controls[i];
+            if (control.CLASS_NAME == this.argParserClass.CLASS_NAME) {
+                
+                // If a permalink is added to the map, and an ArgParser already
+                // exists, we override the displayProjection to be the one
+                // on the permalink. 
+                if (control.displayProjection != this.displayProjection) {
+                    this.displayProjection = control.displayProjection;
+                }    
+                
+                break;
+            }
+        }
+        if (i == this.map.controls.length) {
+            this.map.addControl(new this.argParserClass(
+                { 'displayProjection': this.displayProjection }));       
+        }
+
+    },
+
+    /**
+     * Method: draw
+     *
+     * Returns:
+     * {DOMElement}
+     */    
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+          
+        if (!this.element) {
+            this.div.className = this.displayClass;
+            this.element = document.createElement("a");
+            this.element.innerHTML = OpenLayers.i18n("permalink");
+            this.element.href="";
+            this.div.appendChild(this.element);
+        }
+        this.map.events.on({
+            'moveend': this.updateLink,
+            'changelayer': this.updateLink,
+            'changebaselayer': this.updateLink,
+            scope: this
+        });
+        
+        // Make it so there is at least a link even though the map may not have
+        // moved yet.
+        this.updateLink();
+        
+        return this.div;
+    },
+   
+    /**
+     * Method: updateLink 
+     */
+    updateLink: function() {
+        var href = this.base;
+        if (href.indexOf('?') != -1) {
+            href = href.substring( 0, href.indexOf('?') );
+        }
+
+        href += '?' + OpenLayers.Util.getParameterString(this.createParams());
+        this.element.href = href;
+    }, 
+    
+    /**
+     * APIMethod: createParams
+     * Creates the parameters that need to be encoded into the permalink url.
+     * 
+     * Parameters:
+     * center - {<OpenLayers.LonLat>} center to encode in the permalink.
+     *     Defaults to the current map center.
+     * zoom - {Integer} zoom level to encode in the permalink. Defaults to the
+     *     current map zoom level.
+     * layers - {Array(<OpenLayers.Layer>)} layers to encode in the permalink.
+     *     Defaults to the current map layers.
+     * 
+     * Returns:
+     * {Object} Hash of parameters that will be url-encoded into the
+     * permalink.
+     */
+    createParams: function(center, zoom, layers) {
+        center = center || this.map.getCenter();
+        zoom = zoom || this.map.getZoom();
+        layers = layers || this.map.layers;  
+          
+        var params = OpenLayers.Util.getParameters(this.base);
+        
+        // If there's still no center, map is not initialized yet. 
+        // Break out of this function, and simply return the params from the
+        // base link.
+        if (center) { 
+    
+            params.zoom = this.map.getZoom(); 
+            var lat = center.lat;
+            var lon = center.lon;
+            
+            if (this.displayProjection) {
+                var mapPosition = OpenLayers.Projection.transform(
+                  { x: lon, y: lat }, 
+                  this.map.getProjectionObject(), 
+                  this.displayProjection );
+                lon = mapPosition.x;  
+                lat = mapPosition.y;  
+            }       
+            params.lat = Math.round(lat*100000)/100000;
+            params.lon = Math.round(lon*100000)/100000;
+            
+            params.layers = '';
+            for (var i=0, len=this.map.layers.length; i<len; i++) {
+                var layer = this.map.layers[i];
+    
+                if (layer.isBaseLayer) {
+                    params.layers += (layer == this.map.baseLayer) ? "B" : "0";
+                } else {
+                    params.layers += (layer.getVisibility()) ? "T" : "F";           
+                }
+            }
+        }
+
+        return params;
+    }, 
+
+    CLASS_NAME: "OpenLayers.Control.Permalink"
+});
+/* ======================================================================
+    OpenLayers/Format/JSON.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Note:
+ * This work draws heavily from the public domain JSON serializer/deserializer
+ *     at http://www.json.org/json.js. Rewritten so that it doesn't modify
+ *     basic data prototypes.
+ */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.JSON
+ * A parser to read/write JSON safely.  Create a new instance with the
+ *     <OpenLayers.Format.JSON> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, {
+    
+    /**
+     * APIProperty: indent
+     * {String} For "pretty" printing, the indent string will be used once for
+     *     each indentation level.
+     */
+    indent: "    ",
+    
+    /**
+     * APIProperty: space
+     * {String} For "pretty" printing, the space string will be used after
+     *     the ":" separating a name/value pair.
+     */
+    space: " ",
+    
+    /**
+     * APIProperty: newline
+     * {String} For "pretty" printing, the newline string will be used at the
+     *     end of each name/value pair or array item.
+     */
+    newline: "\n",
+    
+    /**
+     * Property: level
+     * {Integer} For "pretty" printing, this is incremented/decremented during
+     *     serialization.
+     */
+    level: 0,
+
+    /**
+     * Property: pretty
+     * {Boolean} Serialize with extra whitespace for structure.  This is set
+     *     by the <write> method.
+     */
+    pretty: false,
+
+    /**
+     * Constructor: OpenLayers.Format.JSON
+     * Create a new parser for JSON.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: read
+     * Deserialize a json string.
+     *
+     * Parameters:
+     * json - {String} A JSON string
+     * filter - {Function} A function which will be called for every key and
+     *     value at every level of the final result. Each value will be
+     *     replaced by the result of the filter function. This can be used to
+     *     reform generic objects into instances of classes, or to transform
+     *     date strings into Date objects.
+     *     
+     * Returns:
+     * {Object} An object, array, string, or number .
+     */
+    read: function(json, filter) {
+        /**
+         * Parsing happens in three stages. In the first stage, we run the text
+         *     against a regular expression which looks for non-JSON
+         *     characters. We are especially concerned with '()' and 'new'
+         *     because they can cause invocation, and '=' because it can cause
+         *     mutation. But just to be safe, we will reject all unexpected
+         *     characters.
+         */
+        try {
+            if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
+                                replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+                                replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+                /**
+                 * In the second stage we use the eval function to compile the
+                 *     text into a JavaScript structure. The '{' operator is
+                 *     subject to a syntactic ambiguity in JavaScript - it can
+                 *     begin a block or an object literal. We wrap the text in
+                 *     parens to eliminate the ambiguity.
+                 */
+                var object = eval('(' + json + ')');
+
+                /**
+                 * In the optional third stage, we recursively walk the new
+                 *     structure, passing each name/value pair to a filter
+                 *     function for possible transformation.
+                 */
+                if(typeof filter === 'function') {
+                    function walk(k, v) {
+                        if(v && typeof v === 'object') {
+                            for(var i in v) {
+                                if(v.hasOwnProperty(i)) {
+                                    v[i] = walk(i, v[i]);
+                                }
+                            }
+                        }
+                        return filter(k, v);
+                    }
+                    object = walk('', object);
+                }
+                return object;
+            }
+        } catch(e) {
+            // Fall through if the regexp test fails.
+        }
+        return null;
+    },
+
+    /**
+     * APIMethod: write
+     * Serialize an object into a JSON string.
+     *
+     * Parameters:
+     * value - {String} The object, array, string, number, boolean or date
+     *     to be serialized.
+     * pretty - {Boolean} Structure the output with newlines and indentation.
+     *     Default is false.
+     *
+     * Returns:
+     * {String} The JSON string representation of the input value.
+     */
+    write: function(value, pretty) {
+        this.pretty = !!pretty;
+        var json = null;
+        var type = typeof value;
+        if(this.serialize[type]) {
+            json = this.serialize[type].apply(this, [value]);
+        }
+        return json;
+    },
+    
+    /**
+     * Method: writeIndent
+     * Output an indentation string depending on the indentation level.
+     *
+     * Returns:
+     * {String} An appropriate indentation string.
+     */
+    writeIndent: function() {
+        var pieces = [];
+        if(this.pretty) {
+            for(var i=0; i<this.level; ++i) {
+                pieces.push(this.indent);
+            }
+        }
+        return pieces.join('');
+    },
+    
+    /**
+     * Method: writeNewline
+     * Output a string representing a newline if in pretty printing mode.
+     *
+     * Returns:
+     * {String} A string representing a new line.
+     */
+    writeNewline: function() {
+        return (this.pretty) ? this.newline : '';
+    },
+    
+    /**
+     * Method: writeSpace
+     * Output a string representing a space if in pretty printing mode.
+     *
+     * Returns:
+     * {String} A space.
+     */
+    writeSpace: function() {
+        return (this.pretty) ? this.space : '';
+    },
+
+    /**
+     * Property: serialize
+     * Object with properties corresponding to the serializable data types.
+     *     Property values are functions that do the actual serializing.
+     */
+    serialize: {
+        /**
+         * Method: serialize.object
+         * Transform an object into a JSON string.
+         *
+         * Parameters:
+         * object - {Object} The object to be serialized.
+         * 
+         * Returns:
+         * {String} A JSON string representing the object.
+         */
+        'object': function(object) {
+            // three special objects that we want to treat differently
+            if(object == null) {
+                return "null";
+            }
+            if(object.constructor == Date) {
+                return this.serialize.date.apply(this, [object]);
+            }
+            if(object.constructor == Array) {
+                return this.serialize.array.apply(this, [object]);
+            }
+            var pieces = ['{'];
+            this.level += 1;
+            var key, keyJSON, valueJSON;
+            
+            var addComma = false;
+            for(key in object) {
+                if(object.hasOwnProperty(key)) {
+                    // recursive calls need to allow for sub-classing
+                    keyJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+                                                    [key, this.pretty]);
+                    valueJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+                                                    [object[key], this.pretty]);
+                    if(keyJSON != null && valueJSON != null) {
+                        if(addComma) {
+                            pieces.push(',');
+                        }
+                        pieces.push(this.writeNewline(), this.writeIndent(),
+                                    keyJSON, ':', this.writeSpace(), valueJSON);
+                        addComma = true;
+                    }
+                }
+            }
+            
+            this.level -= 1;
+            pieces.push(this.writeNewline(), this.writeIndent(), '}');
+            return pieces.join('');
+        },
+        
+        /**
+         * Method: serialize.array
+         * Transform an array into a JSON string.
+         *
+         * Parameters:
+         * array - {Array} The array to be serialized
+         * 
+         * Returns:
+         * {String} A JSON string representing the array.
+         */
+        'array': function(array) {
+            var json;
+            var pieces = ['['];
+            this.level += 1;
+    
+            for(var i=0, len=array.length; i<len; ++i) {
+                // recursive calls need to allow for sub-classing
+                json = OpenLayers.Format.JSON.prototype.write.apply(this,
+                                                    [array[i], this.pretty]);
+                if(json != null) {
+                    if(i > 0) {
+                        pieces.push(',');
+                    }
+                    pieces.push(this.writeNewline(), this.writeIndent(), json);
+                }
+            }
+
+            this.level -= 1;    
+            pieces.push(this.writeNewline(), this.writeIndent(), ']');
+            return pieces.join('');
+        },
+        
+        /**
+         * Method: serialize.string
+         * Transform a string into a JSON string.
+         *
+         * Parameters:
+         * string - {String} The string to be serialized
+         * 
+         * Returns:
+         * {String} A JSON string representing the string.
+         */
+        'string': function(string) {
+            // If the string contains no control characters, no quote characters, and no
+            // backslash characters, then we can simply slap some quotes around it.
+            // Otherwise we must also replace the offending characters with safe
+            // sequences.    
+            var m = {
+                '\b': '\\b',
+                '\t': '\\t',
+                '\n': '\\n',
+                '\f': '\\f',
+                '\r': '\\r',
+                '"' : '\\"',
+                '\\': '\\\\'
+            };
+            if(/["\\\x00-\x1f]/.test(string)) {
+                return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+                    var c = m[b];
+                    if(c) {
+                        return c;
+                    }
+                    c = b.charCodeAt();
+                    return '\\u00' +
+                        Math.floor(c / 16).toString(16) +
+                        (c % 16).toString(16);
+                }) + '"';
+            }
+            return '"' + string + '"';
+        },
+
+        /**
+         * Method: serialize.number
+         * Transform a number into a JSON string.
+         *
+         * Parameters:
+         * number - {Number} The number to be serialized.
+         *
+         * Returns:
+         * {String} A JSON string representing the number.
+         */
+        'number': function(number) {
+            return isFinite(number) ? String(number) : "null";
+        },
+        
+        /**
+         * Method: serialize.boolean
+         * Transform a boolean into a JSON string.
+         *
+         * Parameters:
+         * bool - {Boolean} The boolean to be serialized.
+         * 
+         * Returns:
+         * {String} A JSON string representing the boolean.
+         */
+        'boolean': function(bool) {
+            return String(bool);
+        },
+        
+        /**
+         * Method: serialize.object
+         * Transform a date into a JSON string.
+         *
+         * Parameters:
+         * date - {Date} The date to be serialized.
+         * 
+         * Returns:
+         * {String} A JSON string representing the date.
+         */
+        'date': function(date) {    
+            function format(number) {
+                // Format integers to have at least two digits.
+                return (number < 10) ? '0' + number : number;
+            }
+            return '"' + date.getFullYear() + '-' +
+                    format(date.getMonth() + 1) + '-' +
+                    format(date.getDate()) + 'T' +
+                    format(date.getHours()) + ':' +
+                    format(date.getMinutes()) + ':' +
+                    format(date.getSeconds()) + '"';
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Format.JSON" 
+
+});     
+/* ======================================================================
+    OpenLayers/Format/XML.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XML
+ * Read and write XML.  For cross-browser XML generation, use methods on an
+ *     instance of the XML format class instead of on <code>document<end>.
+ *     The DOM creation and traversing methods exposed here all mimic the
+ *     W3C XML DOM methods.  Create a new parser with the
+ *     <OpenLayers.Format.XML> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, {
+    
+    /**
+     * Property: xmldom
+     * {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM
+     *     object.  It is not intended to be a browser sniffing property.
+     *     Instead, the xmldom property is used instead of <code>document<end>
+     *     where namespaced node creation methods are not supported. In all
+     *     other browsers, this remains null.
+     */
+    xmldom: null,
+
+    /**
+     * Constructor: OpenLayers.Format.XML
+     * Construct an XML parser.  The parser is used to read and write XML.
+     *     Reading XML from a string returns a DOM element.  Writing XML from
+     *     a DOM element returns a string.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on
+     *     the object.
+     */
+    initialize: function(options) {
+        if(window.ActiveXObject) {
+            this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
+        }
+        OpenLayers.Format.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: read
+     * Deserialize a XML string and return a DOM node.
+     *
+     * Parameters:
+     * text - {String} A XML string
+     
+     * Returns:
+     * {DOMElement} A DOM node
+     */
+    read: function(text) {
+        var index = text.indexOf('<');
+        if(index > 0) {
+            text = text.substring(index);
+        }
+        var node = OpenLayers.Util.Try(
+            OpenLayers.Function.bind((
+                function() {
+                    var xmldom;
+                    /**
+                     * Since we want to be able to call this method on the prototype
+                     * itself, this.xmldom may not exist even if in IE.
+                     */
+                    if(window.ActiveXObject && !this.xmldom) {
+                        xmldom = new ActiveXObject("Microsoft.XMLDOM");
+                    } else {
+                        xmldom = this.xmldom;
+                        
+                    }
+                    xmldom.loadXML(text);
+                    return xmldom;
+                }
+            ), this),
+            function() {
+                return new DOMParser().parseFromString(text, 'text/xml');
+            },
+            function() {
+                var req = new XMLHttpRequest();
+                req.open("GET", "data:" + "text/xml" +
+                         ";charset=utf-8," + encodeURIComponent(text), false);
+                if(req.overrideMimeType) {
+                    req.overrideMimeType("text/xml");
+                }
+                req.send(null);
+                return req.responseXML;
+            }
+        );
+        return node;
+    },
+
+    /**
+     * APIMethod: write
+     * Serialize a DOM node into a XML string.
+     * 
+     * Parameters:
+     * node - {DOMElement} A DOM node.
+     *
+     * Returns:
+     * {String} The XML string representation of the input node.
+     */
+    write: function(node) {
+        var data;
+        if(this.xmldom) {
+            data = node.xml;
+        } else {
+            var serializer = new XMLSerializer();
+            if (node.nodeType == 1) {
+                // Add nodes to a document before serializing. Everything else
+                // is serialized as is. This may need more work. See #1218 .
+                var doc = document.implementation.createDocument("", "", null);
+                if (doc.importNode) {
+                    node = doc.importNode(node, true);
+                }
+                doc.appendChild(node);
+                data = serializer.serializeToString(doc);
+            } else {
+                data = serializer.serializeToString(node);
+            }
+        }
+        return data;
+    },
+
+    /**
+     * APIMethod: createElementNS
+     * Create a new element with namespace.  This node can be appended to
+     *     another node with the standard node.appendChild method.  For
+     *     cross-browser support, this method must be used instead of
+     *     document.createElementNS.
+     *
+     * Parameters:
+     * uri - {String} Namespace URI for the element.
+     * name - {String} The qualified name of the element (prefix:localname).
+     * 
+     * Returns:
+     * {Element} A DOM element with namespace.
+     */
+    createElementNS: function(uri, name) {
+        var element;
+        if(this.xmldom) {
+            if(typeof uri == "string") {
+                element = this.xmldom.createNode(1, name, uri);
+            } else {
+                element = this.xmldom.createNode(1, name, "");
+            }
+        } else {
+            element = document.createElementNS(uri, name);
+        }
+        return element;
+    },
+
+    /**
+     * APIMethod: createTextNode
+     * Create a text node.  This node can be appended to another node with
+     *     the standard node.appendChild method.  For cross-browser support,
+     *     this method must be used instead of document.createTextNode.
+     * 
+     * Parameters:
+     * text - {String} The text of the node.
+     * 
+     * Returns: 
+     * {DOMElement} A DOM text node.
+     */
+    createTextNode: function(text) {
+        var node;
+        if(this.xmldom) {
+            node = this.xmldom.createTextNode(text);
+        } else {
+            node = document.createTextNode(text);
+        }
+        return node;
+    },
+
+    /**
+     * APIMethod: getElementsByTagNameNS
+     * Get a list of elements on a node given the namespace URI and local name.
+     *     To return all nodes in a given namespace, use '*' for the name
+     *     argument.  To return all nodes of a given (local) name, regardless
+     *     of namespace, use '*' for the uri argument.
+     * 
+     * Parameters:
+     * node - {Element} Node on which to search for other nodes.
+     * uri - {String} Namespace URI.
+     * name - {String} Local name of the tag (without the prefix).
+     * 
+     * Returns:
+     * {NodeList} A node list or array of elements.
+     */
+    getElementsByTagNameNS: function(node, uri, name) {
+        var elements = [];
+        if(node.getElementsByTagNameNS) {
+            elements = node.getElementsByTagNameNS(uri, name);
+        } else {
+            // brute force method
+            var allNodes = node.getElementsByTagName("*");
+            var potentialNode, fullName;
+            for(var i=0, len=allNodes.length; i<len; ++i) {
+                potentialNode = allNodes[i];
+                fullName = (potentialNode.prefix) ?
+                           (potentialNode.prefix + ":" + name) : name;
+                if((name == "*") || (fullName == potentialNode.nodeName)) {
+                    if((uri == "*") || (uri == potentialNode.namespaceURI)) {
+                        elements.push(potentialNode);
+                    }
+                }
+            }
+        }
+        return elements;
+    },
+
+    /**
+     * APIMethod: getAttributeNodeNS
+     * Get an attribute node given the namespace URI and local name.
+     * 
+     * Parameters:
+     * node - {Element} Node on which to search for attribute nodes.
+     * uri - {String} Namespace URI.
+     * name - {String} Local name of the attribute (without the prefix).
+     * 
+     * Returns:
+     * {DOMElement} An attribute node or null if none found.
+     */
+    getAttributeNodeNS: function(node, uri, name) {
+        var attributeNode = null;
+        if(node.getAttributeNodeNS) {
+            attributeNode = node.getAttributeNodeNS(uri, name);
+        } else {
+            var attributes = node.attributes;
+            var potentialNode, fullName;
+            for(var i=0, len=attributes.length; i<len; ++i) {
+                potentialNode = attributes[i];
+                if(potentialNode.namespaceURI == uri) {
+                    fullName = (potentialNode.prefix) ?
+                               (potentialNode.prefix + ":" + name) : name;
+                    if(fullName == potentialNode.nodeName) {
+                        attributeNode = potentialNode;
+                        break;
+                    }
+                }
+            }
+        }
+        return attributeNode;
+    },
+
+    /**
+     * APIMethod: getAttributeNS
+     * Get an attribute value given the namespace URI and local name.
+     * 
+     * Parameters:
+     * node - {Element} Node on which to search for an attribute.
+     * uri - {String} Namespace URI.
+     * name - {String} Local name of the attribute (without the prefix).
+     * 
+     * Returns:
+     * {String} An attribute value or and empty string if none found.
+     */
+    getAttributeNS: function(node, uri, name) {
+        var attributeValue = "";
+        if(node.getAttributeNS) {
+            attributeValue = node.getAttributeNS(uri, name) || "";
+        } else {
+            var attributeNode = this.getAttributeNodeNS(node, uri, name);
+            if(attributeNode) {
+                attributeValue = attributeNode.nodeValue;
+            }
+        }
+        return attributeValue;
+    },
+    
+    /**
+     * APIMethod: getChildValue
+     * Get the value of the first child node if it exists, or return an
+     *     optional default string.  Returns an empty string if no first child
+     *     exists and no default value is supplied.
+     *
+     * Parameters:
+     * node - {DOMElement} The element used to look for a first child value.
+     * def - {String} Optional string to return in the event that no
+     *     first child value exists.
+     *
+     * Returns:
+     * {String} The value of the first child of the given node.
+     */
+    getChildValue: function(node, def) {
+        var value;
+        if (node && node.firstChild && node.firstChild.nodeValue) { 
+            value = node.firstChild.nodeValue;
+        } else {
+            value = (def != undefined) ? def : "";
+        }
+        return value;
+    },
+
+    /**
+     * APIMethod: concatChildValues
+     * Concatenate the value of all child nodes if any exist, or return an
+     *     optional default string.  Returns an empty string if no children
+     *     exist and no default value is supplied.  Not optimized for large
+     *     numbers of child nodes.
+     *
+     * Parameters:
+     * node - {DOMElement} The element used to look for child values.
+     * def - {String} Optional string to return in the event that no
+     *     child exist.
+     *
+     * Returns:
+     * {String} The concatenated value of all child nodes of the given node.
+     */
+    concatChildValues: function(node, def) {
+        var value = "";
+        var child = node.firstChild;
+        var childValue;
+        while(child) {
+            childValue = child.nodeValue;
+            if(childValue) {
+                value += childValue;
+            }
+            child = child.nextSibling;
+        }
+        if(value == "" && def != undefined) {
+            value = def;
+        }
+        return value;
+    },
+
+    /**
+     * APIMethod: hasAttributeNS
+     * Determine whether a node has a particular attribute matching the given
+     *     name and namespace.
+     * 
+     * Parameters:
+     * node - {Element} Node on which to search for an attribute.
+     * uri - {String} Namespace URI.
+     * name - {String} Local name of the attribute (without the prefix).
+     * 
+     * Returns:
+     * {Boolean} The node has an attribute matching the name and namespace.
+     */
+    hasAttributeNS: function(node, uri, name) {
+        var found = false;
+        if(node.hasAttributeNS) {
+            found = node.hasAttributeNS(uri, name);
+        } else {
+            found = !!this.getAttributeNodeNS(node, uri, name);
+        }
+        return found;
+    },
+    
+    /**
+     * APIMethod: setAttributeNS
+     * Adds a new attribute or changes the value of an attribute with the given
+     *     namespace and name.
+     *
+     * Parameters:
+     * node - {Element} Element node on which to set the attribute.
+     * uri - {String} Namespace URI for the attribute.
+     * name - {String} Qualified name (prefix:localname) for the attribute.
+     * value - {String} Attribute value.
+     */
+    setAttributeNS: function(node, uri, name, value) {
+        if(node.setAttributeNS) {
+            node.setAttributeNS(uri, name, value);
+        } else {
+            if(this.xmldom) {
+                if(uri) {
+                    var attribute = node.ownerDocument.createNode(
+                        2, name, uri
+                    );
+                    attribute.nodeValue = value;
+                    node.setAttributeNode(attribute);
+                } else {
+                    node.setAttribute(name, value);
+                }
+            } else {
+                throw "setAttributeNS not implemented";
+            }
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Format.XML" 
+
+});     
+/* ======================================================================
     OpenLayers/Handler.js
    ====================================================================== */
 
@@ -7768,7 +16070,7 @@
         }
         // register for event handlers defined on this class.
         var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
-        for (var i = 0; i < events.length; i++) {
+        for (var i=0, len=events.length; i<len; i++) {
             if (this[events[i]]) {
                 this.register(events[i], this[events[i]]); 
             }
@@ -7790,7 +16092,7 @@
         }
         // unregister event handlers defined on this class.
         var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
-        for (var i = 0; i < events.length; i++) {
+        for (var i=0, len=events.length; i<len; i++) {
             if (this[events[i]]) {
                 this.unregister(events[i], this[events[i]]); 
             }
@@ -7924,7 +16226,13 @@
      * Constant: Z_INDEX_BASE
      * {Object} Base z-indexes for different classes of thing 
      */
-    Z_INDEX_BASE: { BaseLayer: 100, Overlay: 325, Popup: 750, Control: 1000 },
+    Z_INDEX_BASE: {
+        BaseLayer: 100,
+        Overlay: 325,
+        Feature: 725,
+        Popup: 750,
+        Control: 1000
+    },
 
     /**
      * Constant: EVENT_TYPES
@@ -7964,6 +16272,7 @@
      *  - *movestart* triggered after the start of a drag, pan, or zoom
      *  - *move* triggered after each drag, pan, or zoom
      *  - *moveend* triggered after a drag, pan, or zoom completes
+     *  - *zoomend* triggered after a zoom completes
      *  - *popupopen* triggered after a popup opens
      *  - *popupclose* triggered after a popup opens
      *  - *addmarker* triggered after a marker has been added
@@ -8097,6 +16406,13 @@
     zoom: 0,    
 
     /**
+     * Property: panRatio
+     * {Float} The ratio of the current extent within
+     *         which panning will tween.
+     */
+    panRatio: 1.5,    
+
+    /**
      * Property: viewRequestID
      * {String} Used to store a unique identifier that changes when the map 
      *          view changes. viewRequestID should be used when adding data 
@@ -8304,6 +16620,7 @@
         this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
 
         this.div = OpenLayers.Util.getElement(div);
+        OpenLayers.Element.addClass(this.div, 'olMap');
 
         // the viewPortDiv is the outermost div we modify
         var id = this.div.id + "_OpenLayers_ViewPort";
@@ -8325,7 +16642,8 @@
         this.events = new OpenLayers.Events(this, 
                                             this.div, 
                                             this.EVENT_TYPES, 
-                                            this.fallThrough);
+                                            this.fallThrough, 
+                                            {includeXY: true});
         this.updateSize();
         if(this.eventListeners instanceof Object) {
             this.events.on(this.eventListeners);
@@ -8354,7 +16672,7 @@
             // check existing links for equivalent url
             var addNode = true;
             var nodes = document.getElementsByTagName('link');
-            for(var i=0; i<nodes.length; ++i) {
+            for(var i=0, len=nodes.length; i<len; ++i) {
                 if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
                                                    this.theme)) {
                     addNode = false;
@@ -8386,7 +16704,7 @@
             }
         }
 
-        for(var i=0; i < this.controls.length; i++) {
+        for(var i=0, len=this.controls.length; i<len; i++) {
             this.addControlToMap(this.controls[i]);
         }
 
@@ -8639,7 +16957,7 @@
      */
     getLayer: function(id) {
         var foundLayer = null;
-        for (var i = 0; i < this.layers.length; i++) {
+        for (var i=0, len=this.layers.length; i<len; i++) {
             var layer = this.layers[i];
             if (layer.id == id) {
                 foundLayer = layer;
@@ -8667,7 +16985,7 @@
      * Reset each layer's z-index based on layer's array index
      */
     resetLayersZIndex: function() {
-        for (var i = 0; i < this.layers.length; i++) {
+        for (var i=0, len=this.layers.length; i<len; i++) {
             var layer = this.layers[i];
             this.setLayerZIndex(layer, i);
         }
@@ -8680,7 +16998,7 @@
     * layer - {<OpenLayers.Layer>} 
     */    
     addLayer: function (layer) {
-        for(var i=0; i < this.layers.length; i++) {
+        for(var i=0, len=this.layers.length; i <len; i++) {
             if (this.layers[i] == layer) {
                 var msg = OpenLayers.i18n('layerAlreadyAdded', 
                                                       {'layerName':layer.name});
@@ -8724,7 +17042,7 @@
     * layers - {Array(<OpenLayers.Layer>)} 
     */    
     addLayers: function (layers) {
-        for (var i = 0; i <  layers.length; i++) {
+        for (var i=0, len=layers.length; i<len; i++) {
             this.addLayer(layers[i]);
         }
     },
@@ -8775,7 +17093,7 @@
         if(this.baseLayer == layer) {
             this.baseLayer = null;
             if(setNewBaseLayer) {
-                for(var i=0; i < this.layers.length; i++) {
+                for(var i=0, len=this.layers.length; i<len; i++) {
                     var iLayer = this.layers[i];
                     if (iLayer.isBaseLayer) {
                         this.setBaseLayer(iLayer);
@@ -8836,7 +17154,7 @@
         if (base != idx) {
             this.layers.splice(base, 1);
             this.layers.splice(idx, 0, layer);
-            for (var i = 0; i < this.layers.length; i++) {
+            for (var i=0, len=this.layers.length; i<len; i++) {
                 this.setLayerZIndex(this.layers[i], i);
             }
             this.events.triggerEvent("changelayer", {
@@ -8854,7 +17172,7 @@
      *
      * Paremeters:
      * layer - {<OpenLayers.Layer>} 
-     * idx - {int} 
+     * delta - {int} 
      */
     raiseLayer: function (layer, delta) {
         var idx = this.getLayerIndex(layer) + delta;
@@ -8986,7 +17304,7 @@
      */    
     getControl: function (id) {
         var returnControl = null;
-        for(var i=0; i < this.controls.length; i++) {
+        for(var i=0, len=this.controls.length; i<len; i++) {
             var control = this.controls[i];
             if (control.id == id) {
                 returnControl = control;
@@ -9101,7 +17419,7 @@
      */
     updateSize: function() {
         // the div might have moved on the page, also
-        this.events.element.offsets = null;
+        this.events.clearMouseCache();
         var newSize = this.getCurrentSize();
         var oldSize = this.getSize();
         if (oldSize == null) {
@@ -9113,7 +17431,7 @@
             this.size = newSize;
 
             //notify layers of mapresize
-            for(var i=0; i < this.layers.length; i++) {
+            for(var i=0, len=this.layers.length; i<len; i++) {
                 this.layers[i].onMapResize();                
             }
 
@@ -9235,11 +17553,7 @@
      *    false.
      */
     pan: function(dx, dy, options) {
-        // this should be pushed to applyDefaults and extend
-        if (!options) {
-            options = {};
-        }
-        OpenLayers.Util.applyDefaults(options, {
+        options = OpenLayers.Util.applyDefaults(options, {
             animate: true,
             dragging: false
         });
@@ -9270,11 +17584,18 @@
      * lonlat - {<OpenLayers.Lonlat>}
      */
     panTo: function(lonlat) {
-        if (this.panMethod && this.getExtent().containsLonLat(lonlat)) {
+        if (this.panMethod && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
             if (!this.panTween) {
                 this.panTween = new OpenLayers.Tween(this.panMethod);
             }
             var center = this.getCenter();
+
+            // center will not change, don't do nothing
+            if (lonlat.lon == center.lon &&
+                lonlat.lat == center.lat) {
+                return;
+            }
+
             var from = {
                 lon: center.lon,
                 lat: center.lat
@@ -9436,7 +17757,7 @@
             
             bounds = this.baseLayer.getExtent();
             
-            for (var i = 0; i < this.layers.length; i++) {
+            for (var i=0, len=this.layers.length; i<len; i++) {
                 var layer = this.layers[i];
                 if (!layer.isBaseLayer) {
                     var inRange = layer.calculateInRange();
@@ -9455,13 +17776,16 @@
                     }
                     if (inRange && layer.visibility) {
                         layer.moveTo(bounds, zoomChanged, dragging);
+                        layer.events.triggerEvent("moveend",
+                            {"zoomChanged": zoomChanged}
+                        );
                     }
                 }                
             }
             
             if (zoomChanged) {
                 //redraw popups
-                for (var i = 0; i < this.popups.length; i++) {
+                for (var i=0, len=this.popups.length; i<len; i++) {
                     this.popups[i].updatePosition();
                 }
             }    
@@ -9591,13 +17915,25 @@
         
     /**
      * APIMethod: getMaxExtent
+     *
+     * Parameters:
+     * options - {Object} 
      * 
+     * Allowed Options:
+     * restricted - {Boolean} If true, returns restricted extent (if it is 
+     *     available.)
+     *
      * Returns:
-     * {<OpenLayers.Bounds>}
+     * {<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 () {
+    getMaxExtent: function (options) {
         var maxExtent = null;
-        if (this.baseLayer != null) {
+        if(options && options.restricted && this.restrictedExtent){
+            maxExtent = this.restrictedExtent;
+        } else if (this.baseLayer != null) {
             maxExtent = this.baseLayer.maxExtent;
         }        
         return maxExtent;
@@ -9660,6 +17996,21 @@
         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
       * 
@@ -9792,8 +18143,13 @@
      * 
      * 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.
+     * 
      */
-    zoomToExtent: function(bounds) {
+    zoomToExtent: function(bounds, closest) {
         var center = bounds.getCenterLonLat();
         if (this.baseLayer.wrapDateLine) {
             var maxExtent = this.getMaxExtent();
@@ -9817,15 +18173,28 @@
             //
             center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
         }
-        this.setCenter(center, this.getZoomForExtent(bounds));
+        this.setCenter(center, this.getZoomForExtent(bounds, closest));
     },
 
     /** 
      * APIMethod: zoomToMaxExtent
      * Zoom to the full extent and recenter.
+     *
+     * Parameters:
+     * options - 
+     * 
+     * Allowed Options:
+     * restricted - {Boolean} True to zoom to restricted extent if it is 
+     *     set. Defaults to true.
      */
-    zoomToMaxExtent: function() {
-        this.zoomToExtent(this.getMaxExtent());
+    zoomToMaxExtent: function(options) {
+        //restricted is true by default
+        var restricted = (options) ? options.restricted : true;
+
+        var maxExtent = this.getMaxExtent({
+            'restricted': restricted 
+        });
+        this.zoomToExtent(maxExtent);
     },
 
     /** 
@@ -9834,8 +18203,13 @@
      * 
      * 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) {
+    zoomToScale: function(scale, closest) {
         var res = OpenLayers.Util.getResolutionFromScale(scale, 
                                                          this.baseLayer.units);
         var size = this.getSize();
@@ -9847,7 +18221,7 @@
                                            center.lat - h_deg / 2,
                                            center.lon + w_deg / 2,
                                            center.lat + h_deg / 2);
-        this.zoomToExtent(extent);
+        this.zoomToExtent(extent, closest);
     },
     
   /********************************************************/
@@ -10108,9 +18482,9 @@
     
     /** 
      * Constructor: OpenLayers.Marker
-     * Paraemeters:
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>} the position of this marker
      * icon - {<OpenLayers.Icon>}  the icon for this marker
-     * lonlat - {<OpenLayers.LonLat>} the position of this marker
      */
     initialize: function(lonlat, icon) {
         this.lonlat = lonlat;
@@ -10262,6 +18636,2223 @@
     
 
 /* ======================================================================
+    OpenLayers/Popup/AnchoredBubble.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Popup/Anchored.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.AnchoredBubble
+ * 
+ * Inherits from: 
+ *  - <OpenLayers.Popup.Anchored>
+ */
+OpenLayers.Popup.AnchoredBubble = 
+  OpenLayers.Class(OpenLayers.Popup.Anchored, {
+
+    /**
+     * Property: rounded
+     * {Boolean} Has the popup been rounded yet?
+     */
+    rounded: false, 
+    
+    /** 
+     * Constructor: OpenLayers.Popup.AnchoredBubble
+     * 
+     * Parameters:
+     * id - {String}
+     * lonlat - {<OpenLayers.LonLat>}
+     * contentSize - {<OpenLayers.Size>}
+     * contentHTML - {String}
+     * anchor - {Object} Object to which we'll anchor the popup. Must expose 
+     *     a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>) 
+     *     (Note that this is generally an <OpenLayers.Icon>).
+     * closeBox - {Boolean}
+     * closeBoxCallback - {Function} Function to be called on closeBox click.
+     */
+    initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+                        closeBoxCallback) {
+        
+        this.padding = new OpenLayers.Bounds(
+            0, OpenLayers.Popup.AnchoredBubble.CORNER_SIZE,
+            0, OpenLayers.Popup.AnchoredBubble.CORNER_SIZE
+        );
+        OpenLayers.Popup.Anchored.prototype.initialize.apply(this, arguments);
+    },
+
+    /** 
+     * Method: draw
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {DOMElement} Reference to a div that contains the drawn popup.
+     */
+    draw: function(px) {
+        
+        OpenLayers.Popup.Anchored.prototype.draw.apply(this, arguments);
+
+        this.setContentHTML();
+        
+        //set the popup color and opacity           
+        this.setBackgroundColor(); 
+        this.setOpacity();
+
+        return this.div;
+    },
+
+    /**
+     * Method: updateRelativePosition
+     * The popup has been moved to a new relative location, in which case
+     *     we will want to re-do the rico corners.
+     */
+    updateRelativePosition: function() {
+        this.setRicoCorners();
+    },
+
+    /**
+     * APIMethod: setSize
+     * 
+     * Parameters:
+     * contentSize - {<OpenLayers.Size>} the new size for the popup's 
+     *     contents div (in pixels).
+     */
+    setSize:function(contentSize) { 
+        OpenLayers.Popup.Anchored.prototype.setSize.apply(this, arguments);
+
+        this.setRicoCorners();
+    },  
+
+    /**
+     * APIMethod: setBackgroundColor
+     * 
+     * Parameters:
+     * color - {String}
+     */
+    setBackgroundColor:function(color) { 
+        if (color != undefined) {
+            this.backgroundColor = color; 
+        }
+        
+        if (this.div != null) {
+            if (this.contentDiv != null) {
+                this.div.style.background = "transparent";
+                OpenLayers.Rico.Corner.changeColor(this.groupDiv, 
+                                                   this.backgroundColor);
+            }
+        }
+    },  
+    
+    /**
+     * APIMethod: setOpacity
+     * 
+     * Parameters: 
+     * opacity - {float}
+     */
+    setOpacity:function(opacity) { 
+        OpenLayers.Popup.Anchored.prototype.setOpacity.call(this, opacity);
+        
+        if (this.div != null) {
+            if (this.groupDiv != null) {
+                OpenLayers.Rico.Corner.changeOpacity(this.groupDiv, 
+                                                     this.opacity);
+            }
+        }
+    },  
+ 
+    /** 
+     * Method: setBorder
+     * Always sets border to 0. Bubble Popups can not have a border.
+     * 
+     * Parameters:
+     * border - {Integer}
+     */
+    setBorder:function(border) { 
+        this.border = 0;
+    },      
+ 
+    /** 
+     * Method: setRicoCorners
+     * Update RICO corners according to the popup's current relative postion.
+     */
+    setRicoCorners:function() {
+    
+        var corners = this.getCornersToRound(this.relativePosition);
+        var options = {corners: corners,
+                         color: this.backgroundColor,
+                       bgColor: "transparent",
+                         blend: false};
+
+        if (!this.rounded) {
+            OpenLayers.Rico.Corner.round(this.div, options);
+            this.rounded = true;
+        } else {
+            OpenLayers.Rico.Corner.reRound(this.groupDiv, options);
+            //set the popup color and opacity
+            this.setBackgroundColor(); 
+            this.setOpacity();
+        }
+    },
+
+    /** 
+     * Method: getCornersToRound
+     *  
+     * Returns:
+     * {String} The proper corners string ("tr tl bl br") for rico to round.
+     */
+    getCornersToRound:function() {
+
+        var corners = ['tl', 'tr', 'bl', 'br'];
+
+        //we want to round all the corners _except_ the opposite one. 
+        var corner = OpenLayers.Bounds.oppositeQuadrant(this.relativePosition);
+        OpenLayers.Util.removeItem(corners, corner);
+
+        return corners.join(" ");
+    },
+
+    CLASS_NAME: "OpenLayers.Popup.AnchoredBubble"
+});
+
+/**
+ * Constant: CORNER_SIZE
+ * {Integer} 5. Border space for the RICO corners.
+ */
+OpenLayers.Popup.AnchoredBubble.CORNER_SIZE = 5;
+
+/* ======================================================================
+    OpenLayers/Popup/Framed.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Popup/Anchored.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.Framed
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Popup.Anchored>
+ */
+OpenLayers.Popup.Framed =
+  OpenLayers.Class(OpenLayers.Popup.Anchored, {
+
+    /**
+     * Property: imageSrc
+     * {String} location of the image to be used as the popup frame
+     */
+    imageSrc: null,
+
+    /**
+     * Property: imageSize
+     * {<OpenLayers.Size>} Size (measured in pixels) of the image located
+     *     by the 'imageSrc' property.
+     */
+    imageSize: null,
+
+    /**
+     * APIProperty: isAlphaImage
+     * {Boolean} The image has some alpha and thus needs to use the alpha 
+     *     image hack. Note that setting this to true will have no noticeable
+     *     effect in FF or IE7 browsers, but will all but crush the ie6 
+     *     browser. 
+     *     Default is false.
+     */
+    isAlphaImage: false,
+
+    /**
+     * Property: positionBlocks
+     * {Object} Hash of different position blocks (Object/Hashs). Each block 
+     *     will be keyed by a two-character 'relativePosition' 
+     *     code string (ie "tl", "tr", "bl", "br"). Block properties are 
+     *     'offset', 'padding' (self-explanatory), and finally the 'blocks'
+     *     parameter, which is an array of the block objects. 
+     * 
+     *     Each block object must have 'size', 'anchor', and 'position' 
+     *     properties.
+     * 
+     *     Note that positionBlocks should never be modified at runtime.
+     */
+    positionBlocks: null,
+
+    /**
+     * Property: blocks
+     * {Array[Object]} Array of objects, each of which is one "block" of the 
+     *     popup. Each block has a 'div' and an 'image' property, both of 
+     *     which are DOMElements, and the latter of which is appended to the 
+     *     former. These are reused as the popup goes changing positions for
+     *     great economy and elegance.
+     */
+    blocks: null,
+
+    /** 
+     * APIProperty: fixedRelativePosition
+     * {Boolean} We want the framed popup to work dynamically placed relative
+     *     to its anchor but also in just one fixed position. A well designed
+     *     framed popup will have the pixels and logic to display itself in 
+     *     any of the four relative positions, but (understandably), this will
+     *     not be the case for all of them. By setting this property to 'true', 
+     *     framed popup will not recalculate for the best placement each time
+     *     it's open, but will always open the same way. 
+     *     Note that if this is set to true, it is generally advisable to also
+     *     set the 'panIntoView' property to true so that the popup can be 
+     *     scrolled into view (since it will often be offscreen on open)
+     *     Default is false.
+     */
+    fixedRelativePosition: false,
+
+    /** 
+     * Constructor: OpenLayers.Popup.Framed
+     * 
+     * Parameters:
+     * id - {String}
+     * lonlat - {<OpenLayers.LonLat>}
+     * contentSize - {<OpenLayers.Size>}
+     * contentHTML - {String}
+     * anchor - {Object} Object to which we'll anchor the popup. Must expose 
+     *     a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>) 
+     *     (Note that this is generally an <OpenLayers.Icon>).
+     * closeBox - {Boolean}
+     * closeBoxCallback - {Function} Function to be called on closeBox click.
+     */
+    initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox, 
+                        closeBoxCallback) {
+
+        OpenLayers.Popup.Anchored.prototype.initialize.apply(this, arguments);
+
+        if (this.fixedRelativePosition) {
+            //based on our decided relativePostion, set the current padding
+            // this keeps us from getting into trouble 
+            this.updateRelativePosition();
+            
+            //make calculateRelativePosition always returnt the specified
+            // fiexed position.
+            this.calculateRelativePosition = function(px) {
+                return this.relativePosition;
+            };
+        }
+
+        this.contentDiv.style.position = "absolute";
+        this.contentDiv.style.zIndex = 1;
+
+        if (closeBox) {
+            this.closeDiv.style.zIndex = 1;
+        }
+
+        this.groupDiv.style.position = "absolute";
+        this.groupDiv.style.top = "0px";
+        this.groupDiv.style.left = "0px";
+        this.groupDiv.style.height = "100%";
+        this.groupDiv.style.width = "100%";
+    },
+
+    /** 
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        this.imageSrc = null;
+        this.imageSize = null;
+        this.isAlphaImage = null;
+
+        this.fixedRelativePosition = false;
+        this.positionBlocks = null;
+
+        //remove our blocks
+        for(var i = 0; i < this.blocks.length; i++) {
+            var block = this.blocks[i];
+
+            if (block.image) {
+                block.div.removeChild(block.image);
+            }
+            block.image = null;
+
+            if (block.div) {
+                this.groupDiv.removeChild(block.div);
+            }
+            block.div = null;
+        }
+        this.blocks = null;
+
+        OpenLayers.Popup.Anchored.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * APIMethod: setBackgroundColor
+     */
+    setBackgroundColor:function(color) {
+        //does nothing since the framed popup's entire scheme is based on a 
+        // an image -- changing the background color makes no sense. 
+    },
+
+    /**
+     * APIMethod: setBorder
+     */
+    setBorder:function() {
+        //does nothing since the framed popup's entire scheme is based on a 
+        // an image -- changing the popup's border makes no sense. 
+    },
+
+    /**
+     * Method: setOpacity
+     * Sets the opacity of the popup.
+     * 
+     * Parameters:
+     * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).   
+     */
+    setOpacity:function(opacity) {
+        //does nothing since we suppose that we'll never apply an opacity
+        // to a framed popup
+    },
+
+    /**
+     * APIMethod: setSize
+     * Overridden here, because we need to update the blocks whenever the size
+     *     of the popup has changed.
+     * 
+     * Parameters:
+     * contentSize - {<OpenLayers.Size>} the new size for the popup's 
+     *     contents div (in pixels).
+     */
+    setSize:function(contentSize) { 
+        OpenLayers.Popup.Anchored.prototype.setSize.apply(this, arguments);
+
+        this.updateBlocks();
+    },
+
+    /**
+     * Method: updateRelativePosition
+     * When the relative position changes, we need to set the new padding 
+     *     BBOX on the popup, reposition the close div, and update the blocks.
+     */
+    updateRelativePosition: function() {
+
+        //update the padding
+        this.padding = this.positionBlocks[this.relativePosition].padding;
+
+        //update the position of our close box to new padding
+        if (this.closeDiv) {
+            // use the content div's css padding to determine if we should
+            //  padd the close div
+            var contentDivPadding = this.getContentDivPadding();
+
+            this.closeDiv.style.right = contentDivPadding.right + 
+                                        this.padding.right + "px";
+            this.closeDiv.style.top = contentDivPadding.top + 
+                                      this.padding.top + "px";
+        }
+
+        this.updateBlocks();
+    },
+
+    /** 
+     * Method: calculateNewPx
+     * Besides the standard offset as determined by the Anchored class, our 
+     *     Framed popups have a special 'offset' property for each of their 
+     *     positions, which is used to offset the popup relative to its anchor.
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
+     *     relative to the passed-in px.
+     */
+    calculateNewPx:function(px) {
+        var newPx = OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(
+            this, arguments
+        );
+
+        newPx = newPx.offset(this.positionBlocks[this.relativePosition].offset);
+
+        return newPx;
+    },
+
+    /**
+     * Method: createBlocks
+     */
+    createBlocks: function() {
+        this.blocks = [];
+
+        //since all positions contain the same number of blocks, we can 
+        // just pick the first position and use its blocks array to create
+        // our blocks array
+        var firstPosition = null;
+        for(var key in this.positionBlocks) {
+            firstPosition = key;
+            break;
+        }
+        
+        var position = this.positionBlocks[firstPosition];
+        for (var i = 0; i < position.blocks.length; i++) {
+
+            var block = {};
+            this.blocks.push(block);
+
+            var divId = this.id + '_FrameDecorationDiv_' + i;
+            block.div = OpenLayers.Util.createDiv(divId, 
+                null, null, null, "absolute", null, "hidden", null
+            );
+
+            var imgId = this.id + '_FrameDecorationImg_' + i;
+            var imageCreator = 
+                (this.isAlphaImage) ? OpenLayers.Util.createAlphaImageDiv
+                                    : OpenLayers.Util.createImage;
+
+            block.image = imageCreator(imgId, 
+                null, this.imageSize, this.imageSrc, 
+                "absolute", null, null, null
+            );
+
+            block.div.appendChild(block.image);
+            this.groupDiv.appendChild(block.div);
+        }
+    },
+
+    /**
+     * Method: updateBlocks
+     * Internal method, called on initialize and when the popup's relative
+     *     position has changed. This function takes care of re-positioning
+     *     the popup's blocks in their appropropriate places.
+     */
+    updateBlocks: function() {
+        if (!this.blocks) {
+            this.createBlocks();
+        }
+        
+        if (this.size && this.relativePosition) {
+            var position = this.positionBlocks[this.relativePosition];
+            for (var i = 0; i < position.blocks.length; i++) {
+    
+                var positionBlock = position.blocks[i];
+                var block = this.blocks[i];
+    
+                // adjust sizes
+                var l = positionBlock.anchor.left;
+                var b = positionBlock.anchor.bottom;
+                var r = positionBlock.anchor.right;
+                var t = positionBlock.anchor.top;
+    
+                //note that we use the isNaN() test here because if the 
+                // size object is initialized with a "auto" parameter, the 
+                // size constructor calls parseFloat() on the string, 
+                // which will turn it into NaN
+                //
+                var w = (isNaN(positionBlock.size.w)) ? this.size.w - (r + l) 
+                                                      : positionBlock.size.w;
+    
+                var h = (isNaN(positionBlock.size.h)) ? this.size.h - (b + t) 
+                                                      : positionBlock.size.h;
+    
+                block.div.style.width = w + 'px';
+                block.div.style.height = h + 'px';
+    
+                block.div.style.left = (l != null) ? l + 'px' : '';
+                block.div.style.bottom = (b != null) ? b + 'px' : '';
+                block.div.style.right = (r != null) ? r + 'px' : '';            
+                block.div.style.top = (t != null) ? t + 'px' : '';
+    
+                block.image.style.left = positionBlock.position.x + 'px';
+                block.image.style.top = positionBlock.position.y + 'px';
+            }
+    
+            this.contentDiv.style.left = this.padding.left + "px";
+            this.contentDiv.style.top = this.padding.top + "px";
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Popup.Framed"
+});
+/* ======================================================================
+    OpenLayers/Renderer/SVG.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.SVG
+ * 
+ * Inherits:
+ *  - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+    /** 
+     * Property: xmlns
+     * {String}
+     */
+    xmlns: "http://www.w3.org/2000/svg",
+    
+    /**
+     * Property: xlinkns
+     * {String}
+     */
+    xlinkns: "http://www.w3.org/1999/xlink",
+
+    /**
+     * Constant: MAX_PIXEL
+     * {Integer} Firefox has a limitation where values larger or smaller than  
+     *           about 15000 in an SVG document lock the browser up. This 
+     *           works around it.
+     */
+    MAX_PIXEL: 15000,
+
+    /**
+     * Property: translationParameters
+     * {Object} Hash with "x" and "y" properties
+     */
+    translationParameters: null,
+    
+    /**
+     * Property: symbolSize
+     * {Object} Cache for symbol sizes according to their svg coordinate space
+     */
+    symbolSize: {},
+
+    /**
+     * Constructor: OpenLayers.Renderer.SVG
+     * 
+     * Parameters:
+     * containerID - {String}
+     */
+    initialize: function(containerID) {
+        if (!this.supported()) { 
+            return; 
+        }
+        OpenLayers.Renderer.Elements.prototype.initialize.apply(this, 
+                                                                arguments);
+        this.translationParameters = {x: 0, y: 0};
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * APIMethod: supported
+     * 
+     * Returns:
+     * {Boolean} Whether or not the browser supports the SVG renderer
+     */
+    supported: function() {
+        var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
+        return (document.implementation && 
+           (document.implementation.hasFeature("org.w3c.svg", "1.0") || 
+            document.implementation.hasFeature(svgFeature + "SVG", "1.1") || 
+            document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
+    },    
+
+    /**
+     * Method: inValidRange
+     * See #669 for more information
+     *
+     * Parameters:
+     * x      - {Integer}
+     * y      - {Integer}
+     * xyOnly - {Boolean} whether or not to just check for x and y, which means
+     *     to not take the current translation parameters into account if true.
+     * 
+     * Returns:
+     * {Boolean} Whether or not the 'x' and 'y' coordinates are in the  
+     *           valid range.
+     */ 
+    inValidRange: function(x, y, xyOnly) {
+        var left = x + (xyOnly ? 0 : this.translationParameters.x);
+        var top = y + (xyOnly ? 0 : this.translationParameters.y);
+        return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
+                top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
+    },
+
+    /**
+     * Method: setExtent
+     * 
+     * Parameters:
+     * extent - {<OpenLayers.Bounds>}
+     * resolutionChanged - {Boolean}
+     * 
+     * Returns:
+     * {Boolean} true to notify the layer that the new extent does not exceed
+     *     the coordinate range, and the features will not need to be redrawn.
+     *     False otherwise.
+     */
+    setExtent: function(extent, resolutionChanged) {
+        OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, 
+                                                               arguments);
+        
+        var resolution = this.getResolution();
+        var left = -extent.left / resolution;
+        var top = extent.top / resolution;
+
+        // If the resolution has changed, start over changing the corner, because
+        // the features will redraw.
+        if (resolutionChanged) {
+            this.left = left;
+            this.top = top;
+            // Set the viewbox
+            var extentString = "0 0 " + this.size.w + " " + this.size.h;
+
+            this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
+            this.translate(0, 0);
+            return true;
+        } else {
+            var inRange = this.translate(left - this.left, top - this.top);
+            if (!inRange) {
+                // recenter the coordinate system
+                this.setExtent(extent, true);
+            }
+            return inRange;
+        }
+    },
+    
+    /**
+     * Method: translate
+     * Transforms the SVG coordinate system
+     * 
+     * Parameters:
+     * x - {Float}
+     * y - {Float}
+     * 
+     * Returns:
+     * {Boolean} true if the translation parameters ar in the valid coordinates
+     *     range, false otherwise.
+     */
+    translate: function(x, y) {
+        if (!this.inValidRange(x, y, true)) {
+            return false;
+        } else {
+            var transformString = "";
+            if (x || y) {
+                transformString = "translate(" + x + "," + y + ")";
+            }
+            this.root.setAttributeNS(null, "transform", transformString);
+            this.translationParameters = {x: x, y: y};
+            return true;
+        }
+    },
+
+    /**
+     * Method: setSize
+     * Sets the size of the drawing surface.
+     * 
+     * Parameters:
+     * size - {<OpenLayers.Size>} The size of the drawing surface
+     */
+    setSize: function(size) {
+        OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+        
+        this.rendererRoot.setAttributeNS(null, "width", this.size.w);
+        this.rendererRoot.setAttributeNS(null, "height", this.size.h);
+    },
+
+    /** 
+     * Method: getNodeType 
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * 
+     * Returns:
+     * {String} The corresponding node type for the specified geometry
+     */
+    getNodeType: function(geometry, style) {
+        var nodeType = null;
+        switch (geometry.CLASS_NAME) {
+            case "OpenLayers.Geometry.Point":
+                if (style.externalGraphic) {
+                    nodeType = "image";
+                } else if (this.isComplexSymbol(style.graphicName)) {
+                    nodeType = "use";
+                } else {
+                    nodeType = "circle";
+                }
+                break;
+            case "OpenLayers.Geometry.Rectangle":
+                nodeType = "rect";
+                break;
+            case "OpenLayers.Geometry.LineString":
+                nodeType = "polyline";
+                break;
+            case "OpenLayers.Geometry.LinearRing":
+                nodeType = "polygon";
+                break;
+            case "OpenLayers.Geometry.Polygon":
+            case "OpenLayers.Geometry.Curve":
+            case "OpenLayers.Geometry.Surface":
+                nodeType = "path";
+                break;
+            default:
+                break;
+        }
+        return nodeType;
+    },
+
+    /** 
+     * Method: setStyle
+     * Use to set all the style attributes to a SVG node.
+     * 
+     * Takes care to adjust stroke width and point radius to be
+     * resolution-relative
+     *
+     * Parameters:
+     * node - {SVGDomElement} An SVG element to decorate
+     * style - {Object}
+     * options - {Object} Currently supported options include 
+     *                              'isFilled' {Boolean} and
+     *                              'isStroked' {Boolean}
+     */
+    setStyle: function(node, style, options) {
+        style = style  || node._style;
+        options = options || node._options;
+        var r = parseFloat(node.getAttributeNS(null, "r"));
+        var widthFactor = 1;
+        var pos;
+        if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
+            if (style.externalGraphic) {
+                pos = this.getPosition(node);
+                
+                if (style.graphicWidth && style.graphicHeight) {
+                  node.setAttributeNS(null, "preserveAspectRatio", "none");
+                }
+                var width = style.graphicWidth || style.graphicHeight;
+                var height = style.graphicHeight || style.graphicWidth;
+                width = width ? width : style.pointRadius*2;
+                height = height ? height : style.pointRadius*2;
+                var xOffset = (style.graphicXOffset != undefined) ?
+                    style.graphicXOffset : -(0.5 * width);
+                var yOffset = (style.graphicYOffset != undefined) ?
+                    style.graphicYOffset : -(0.5 * height);
+
+                var opacity = style.graphicOpacity || style.fillOpacity;
+                
+                node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
+                node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
+                node.setAttributeNS(null, "width", width);
+                node.setAttributeNS(null, "height", height);
+                node.setAttributeNS(this.xlinkns, "href", style.externalGraphic);
+                node.setAttributeNS(null, "style", "opacity: "+opacity);
+            } else if (this.isComplexSymbol(style.graphicName)) {
+                // the symbol viewBox is three times as large as the symbol
+                var offset = style.pointRadius * 3;
+                var size = offset * 2;
+                var id = this.importSymbol(style.graphicName);
+                var href = "#" + id;
+                pos = this.getPosition(node);
+                widthFactor = this.symbolSize[id] / size;
+                // Only set the href if it is different from the current one.
+                // This is a workaround for strange rendering behavior in FF3.
+                if (node.getAttributeNS(this.xlinkns, "href") != href) {
+                    node.setAttributeNS(this.xlinkns, "href", href);
+                } else if (size != parseFloat(node.getAttributeNS(null, "width"))) {
+                    // hide the element (and force a reflow so it really gets
+                    // hidden. This workaround is needed for Safari.
+                    node.style.visibility = "hidden";
+                    this.container.scrollLeft = this.container.scrollLeft;
+                }
+                node.setAttributeNS(null, "width", size);
+                node.setAttributeNS(null, "height", size);
+                node.setAttributeNS(null, "x", pos.x - offset);
+                node.setAttributeNS(null, "y", pos.y - offset);
+                // set the visibility back to normal (after the Safari
+                // workaround above)
+                node.style.visibility = "";
+            } else {
+                node.setAttributeNS(null, "r", style.pointRadius);
+            }
+
+            if (typeof style.rotation != "undefined" && pos) {
+                var rotation = OpenLayers.String.format(
+                    "rotate(${0} ${1} ${2})", [style.rotation, pos.x, pos.y]);
+                node.setAttributeNS(null, "transform", rotation);
+            }
+        }
+        
+        if (options.isFilled) {
+            node.setAttributeNS(null, "fill", style.fillColor);
+            node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
+        } else {
+            node.setAttributeNS(null, "fill", "none");
+        }
+
+        if (options.isStroked) {
+            node.setAttributeNS(null, "stroke", style.strokeColor);
+            node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
+            node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
+            node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap);
+            // Hard-coded linejoin for now, to make it look the same as in VML.
+            // There is no strokeLinejoin property yet for symbolizers.
+            node.setAttributeNS(null, "stroke-linejoin", "round");
+            node.setAttributeNS(null, "stroke-dasharray", this.dashStyle(style,
+                widthFactor));
+        } else {
+            node.setAttributeNS(null, "stroke", "none");
+        }
+        
+        if (style.pointerEvents) {
+            node.setAttributeNS(null, "pointer-events", style.pointerEvents);
+        }
+        
+        if (style.cursor != null) {
+            node.setAttributeNS(null, "cursor", style.cursor);
+        }
+        return node;
+    },
+
+    /** 
+     * Method: dashStyle
+     * 
+     * Parameters:
+     * style - {Object}
+     * widthFactor - {Number}
+     * 
+     * Returns:
+     * {String} A SVG compliant 'stroke-dasharray' value
+     */
+    dashStyle: function(style, widthFactor) {
+        var w = style.strokeWidth * widthFactor;
+
+        switch (style.strokeDashstyle) {
+            case 'solid':
+                return 'none';
+            case 'dot':
+                return [1, 4 * w].join();
+            case 'dash':
+                return [4 * w, 4 * w].join();
+            case 'dashdot':
+                return [4 * w, 4 * w, 1, 4 * w].join();
+            case 'longdash':
+                return [8 * w, 4 * w].join();
+            case 'longdashdot':
+                return [8 * w, 4 * w, 1, 4 * w].join();
+            default:
+                return style.strokeDashstyle.replace(/ /g, ",");
+        }
+    },
+    
+    /** 
+     * Method: createNode
+     * 
+     * Parameters:
+     * type - {String} Kind of node to draw
+     * id - {String} Id for node
+     * 
+     * Returns:
+     * {DOMElement} A new node of the given type and id
+     */
+    createNode: function(type, id) {
+        var node = document.createElementNS(this.xmlns, type);
+        if (id) {
+            node.setAttributeNS(null, "id", id);
+        }
+        return node;    
+    },
+    
+    /** 
+     * Method: nodeTypeCompare
+     * 
+     * Parameters:
+     * node - {SVGDomElement} An SVG element
+     * type - {String} Kind of node
+     * 
+     * Returns:
+     * {Boolean} Whether or not the specified node is of the specified type
+     */
+    nodeTypeCompare: function(node, type) {
+        return (type == node.nodeName);
+    },
+   
+    /**
+     * Method: createRenderRoot
+     * 
+     * Returns:
+     * {DOMElement} The specific render engine's root element
+     */
+    createRenderRoot: function() {
+        return this.nodeFactory(this.container.id + "_svgRoot", "svg");
+    },
+
+    /**
+     * Method: createRoot
+     * 
+     * Returns:
+     * {DOMElement} The main root element to which we'll add vectors
+     */
+    createRoot: function() {
+        return this.nodeFactory(this.container.id + "_root", "g");
+    },
+
+    /**
+     * Method: createDefs
+     *
+     * Returns:
+     * {DOMElement} The element to which we'll add the symbol definitions
+     */
+    createDefs: function() {
+        var defs = this.nodeFactory("ol-renderer-defs", "defs");
+        this.rendererRoot.appendChild(defs);
+        return defs;
+    },
+
+    /**************************************
+     *                                    *
+     *     GEOMETRY DRAWING FUNCTIONS     *
+     *                                    *
+     **************************************/
+
+    /**
+     * Method: drawPoint
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the point
+     */ 
+    drawPoint: function(node, geometry) {
+        return this.drawCircle(node, geometry, 1);
+    },
+
+    /**
+     * Method: drawCircle
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * radius - {Float}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the circle
+     */
+    drawCircle: function(node, geometry, radius) {
+        var resolution = this.getResolution();
+        var x = (geometry.x / resolution + this.left);
+        var y = (this.top - geometry.y / resolution);
+
+        if (this.inValidRange(x, y)) { 
+            node.setAttributeNS(null, "cx", x);
+            node.setAttributeNS(null, "cy", y);
+            node.setAttributeNS(null, "r", radius);
+            return node;
+        } else {
+            return false;
+        }    
+            
+    },
+    
+    /**
+     * Method: drawLineString
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components of
+     *     the linestring, or false if nothing could be drawn
+     */ 
+    drawLineString: function(node, geometry) {
+        var componentsResult = this.getComponentsString(geometry.components);
+        if (componentsResult.path) {
+            node.setAttributeNS(null, "points", componentsResult.path);
+            return (componentsResult.complete ? node : null);  
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: drawLinearRing
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components
+     *     of the linear ring, or false if nothing could be drawn
+     */ 
+    drawLinearRing: function(node, geometry) {
+        var componentsResult = this.getComponentsString(geometry.components);
+        if (componentsResult.path) {
+            node.setAttributeNS(null, "points", componentsResult.path);
+            return (componentsResult.complete ? node : null);  
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: drawPolygon
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components
+     *     of the polygon, or false if nothing could be drawn
+     */ 
+    drawPolygon: function(node, geometry) {
+        var d = "";
+        var draw = true;
+        var complete = true;
+        var linearRingResult, path;
+        for (var j=0, len=geometry.components.length; j<len; j++) {
+            d += " M";
+            linearRingResult = this.getComponentsString(
+                geometry.components[j].components, " ");
+            path = linearRingResult.path;
+            if (path) {
+                d += " " + path;
+                complete = linearRingResult.complete && complete;
+            } else {
+                draw = false;
+            }
+        }
+        d += " z";
+        if (draw) {
+            node.setAttributeNS(null, "d", d);
+            node.setAttributeNS(null, "fill-rule", "evenodd");
+            return complete ? node : null;
+        } else {
+            return false;
+        }    
+    },
+    
+    /**
+     * Method: drawRectangle
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the rectangle
+     */ 
+    drawRectangle: function(node, geometry) {
+        var resolution = this.getResolution();
+        var x = (geometry.x / resolution + this.left);
+        var y = (this.top - geometry.y / resolution);
+
+        if (this.inValidRange(x, y)) { 
+            node.setAttributeNS(null, "x", x);
+            node.setAttributeNS(null, "y", y);
+            node.setAttributeNS(null, "width", geometry.width / resolution);
+            node.setAttributeNS(null, "height", geometry.height / resolution);
+            return node;
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: drawSurface
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the surface
+     */ 
+    drawSurface: function(node, geometry) {
+
+        // create the svg path string representation
+        var d = null;
+        var draw = true;
+        for (var i=0, len=geometry.components.length; i<len; i++) {
+            if ((i%3) == 0 && (i/3) == 0) {
+                var component = this.getShortString(geometry.components[i]);
+                if (!component) { draw = false; }
+                d = "M " + component;
+            } else if ((i%3) == 1) {
+                var component = this.getShortString(geometry.components[i]);
+                if (!component) { draw = false; }
+                d += " C " + component;
+            } else {
+                var component = this.getShortString(geometry.components[i]);
+                if (!component) { draw = false; }
+                d += " " + component;
+            }
+        }
+        d += " Z";
+        if (draw) {
+            node.setAttributeNS(null, "d", d);
+            return node;
+        } else {
+            return false;
+        }    
+    },
+
+    /** 
+     * Method: getComponentString
+     * 
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
+     * separator - {String} character between coordinate pairs. Defaults to ","
+     * 
+     * Returns:
+     * {Object} hash with properties "path" (the string created from the
+     *     components and "complete" (false if the renderer was unable to
+     *     draw all components)
+     */
+    getComponentsString: function(components, separator) {
+        var renderCmp = [];
+        var complete = true;
+        var len = components.length;
+        var strings = [];
+        var str, component, j;
+        for(var i=0; i<len; i++) {
+            component = components[i];
+            renderCmp.push(component);
+            str = this.getShortString(component);
+            if (str) {
+                strings.push(str);
+            } else {
+                // The current component is outside the valid range. Let's
+                // see if the previous or next component is inside the range.
+                // If so, add the coordinate of the intersection with the
+                // valid range bounds.
+                if (i > 0) {
+                    if (this.getShortString(components[i + 1])) {
+                        strings.push(this.clipLine(components[i],
+                            components[i-1]));
+                    }
+                }
+                if (i < len - 1) {
+                    if (this.getShortString(components[i + 1])) {
+                        strings.push(this.clipLine(components[i],
+                            components[i+1]));
+                    }
+                }
+                complete = false;
+            }
+        }
+
+        return {
+            path: strings.join(separator || ","),
+            complete: complete
+        };
+    },
+    
+    /**
+     * Method: clipLine
+     * Given two points (one inside the valid range, and one outside),
+     * clips the line betweeen the two points so that the new points are both
+     * inside the valid range.
+     * 
+     * Parameters:
+     * badComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
+     *     invalid point
+     * goodComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
+     *     valid point
+     * Returns
+     * {String} the SVG coordinate pair of the clipped point (like
+     *     getShortString), or an empty string if both passed componets are at
+     *     the same point.
+     */
+    clipLine: function(badComponent, goodComponent) {
+        if (goodComponent.equals(badComponent)) {
+            return "";
+        }
+        var resolution = this.getResolution();
+        var maxX = this.MAX_PIXEL - this.translationParameters.x;
+        var maxY = this.MAX_PIXEL - this.translationParameters.y;
+        var x1 = goodComponent.x / resolution + this.left;
+        var y1 = this.top - goodComponent.y / resolution;
+        var x2 = badComponent.x / resolution + this.left;
+        var y2 = this.top - badComponent.y / resolution;
+        var k;
+        if (x2 < -maxX || x2 > maxX) {
+            k = (y2 - y1) / (x2 - x1);
+            x2 = x2 < 0 ? -maxX : maxX;
+            y2 = y1 + (x2 - x1) * k;
+        }
+        if (y2 < -maxY || y2 > maxY) {
+            k = (x2 - x1) / (y2 - y1);
+            y2 = y2 < 0 ? -maxY : maxY;
+            x2 = x1 + (y2 - y1) * k;
+        }
+        return x2 + "," + y2;
+    },
+
+    /** 
+     * Method: getShortString
+     * 
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>}
+     * 
+     * Returns:
+     * {String} or false if point is outside the valid range
+     */
+    getShortString: function(point) {
+        var resolution = this.getResolution();
+        var x = (point.x / resolution + this.left);
+        var y = (this.top - point.y / resolution);
+
+        if (this.inValidRange(x, y)) { 
+            return x + "," + y;
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: getPosition
+     * Finds the position of an svg node.
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * 
+     * Returns:
+     * {Object} hash with x and y properties, representing the coordinates
+     *     within the svg coordinate system
+     */
+    getPosition: function(node) {
+        return({
+            x: parseFloat(node.getAttributeNS(null, "cx")),
+            y: parseFloat(node.getAttributeNS(null, "cy"))
+        });
+    },
+
+    /**
+     * Method: importSymbol
+     * add a new symbol definition from the rendererer's symbol hash
+     * 
+     * Parameters:
+     * graphicName - {String} name of the symbol to import
+     * 
+     * Returns:
+     * {String} - id of the imported symbol
+     */      
+    importSymbol: function (graphicName)  {
+        if (!this.defs) {
+            // create svg defs tag
+            this.defs = this.createDefs();
+        }
+        var id = this.container.id + "-" + graphicName;
+        
+        // check if symbol already exists in the defs
+        if (document.getElementById(id) != null) {
+            return id;
+        }
+        
+        var symbol = OpenLayers.Renderer.symbol[graphicName];
+        if (!symbol) {
+            throw new Error(graphicName + ' is not a valid symbol name');
+            return;
+        }
+
+        var symbolNode = this.nodeFactory(id, "symbol");
+        var node = this.nodeFactory(null, "polygon");
+        symbolNode.appendChild(node);
+        var symbolExtent = new OpenLayers.Bounds(
+                                    Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+        var points = "";
+        var x,y;
+        for (var i=0; i<symbol.length; i=i+2) {
+            x = symbol[i];
+            y = symbol[i+1];
+            symbolExtent.left = Math.min(symbolExtent.left, x);
+            symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+            symbolExtent.right = Math.max(symbolExtent.right, x);
+            symbolExtent.top = Math.max(symbolExtent.top, y);
+            points += " " + x + "," + y;
+        }
+        
+        node.setAttributeNS(null, "points", points);
+        
+        var width = symbolExtent.getWidth();
+        var height = symbolExtent.getHeight();
+        // create a viewBox three times as large as the symbol itself,
+        // to allow for strokeWidth being displayed correctly at the corners.
+        var viewBox = [symbolExtent.left - width,
+                        symbolExtent.bottom - height, width * 3, height * 3];
+        symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
+        this.symbolSize[id] = Math.max(width, height) * 3;
+        
+        this.defs.appendChild(symbolNode);
+        return symbolNode.id;
+    },
+
+    CLASS_NAME: "OpenLayers.Renderer.SVG"
+});
+/* ======================================================================
+    OpenLayers/Renderer/VML.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.VML
+ * Render vector features in browsers with VML capability.  Construct a new
+ * VML renderer with the <OpenLayers.Renderer.VML> constructor.
+ * 
+ * Note that for all calculations in this class, we use toFixed() to round a 
+ * float value to an integer. This is done because it seems that VML doesn't 
+ * support float values.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+    /**
+     * Property: xmlns
+     * {String} XML Namespace URN
+     */
+    xmlns: "urn:schemas-microsoft-com:vml",
+    
+    /**
+     * Property: symbolCache
+     * {DOMElement} node holding symbols. This hash is keyed by symbol name,
+     *     and each value is a hash with a "path" and an "extent" property.
+     */
+    symbolCache: {},
+
+    /**
+     * Constructor: OpenLayers.Renderer.VML
+     * Create a new VML renderer.
+     *
+     * Parameters:
+     * containerID - {String} The id for the element that contains the renderer
+     */
+    initialize: function(containerID) {
+        if (!this.supported()) { 
+            return; 
+        }
+        if (!document.namespaces.olv) {
+            document.namespaces.add("olv", this.xmlns);
+            var style = document.createStyleSheet();
+            style.addRule('olv\\:*', "behavior: url(#default#VML); " +
+                                   "position: absolute; display: inline-block;");
+        }
+        OpenLayers.Renderer.Elements.prototype.initialize.apply(this, 
+                                                                arguments);
+    },
+
+    /**
+     * APIMethod: destroy
+     * Deconstruct the renderer.
+     */
+    destroy: function() {
+        OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * APIMethod: supported
+     * Determine whether a browser supports this renderer.
+     *
+     * Returns:
+     * {Boolean} The browser supports the VML renderer
+     */
+    supported: function() {
+        return !!(document.namespaces);
+    },    
+
+    /**
+     * Method: setExtent
+     * Set the renderer's extent
+     *
+     * Parameters:
+     * extent - {<OpenLayers.Bounds>}
+     * resolutionChanged - {Boolean}
+     * 
+     * Returns:
+     * {Boolean} true to notify the layer that the new extent does not exceed
+     *     the coordinate range, and the features will not need to be redrawn.
+     */
+    setExtent: function(extent, resolutionChanged) {
+        OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, 
+                                                               arguments);
+        var resolution = this.getResolution();
+    
+        var org = extent.left/resolution + " " + 
+                    (extent.top/resolution - this.size.h);
+        this.root.setAttribute("coordorigin", org);
+
+        var size = this.size.w + " " + this.size.h;
+        this.root.setAttribute("coordsize", size);
+        
+        // flip the VML display Y axis upside down so it 
+        // matches the display Y axis of the map
+        this.root.style.flip = "y";
+        
+        return true;
+    },
+
+
+    /**
+     * Method: setSize
+     * Set the size of the drawing surface
+     *
+     * Parameters:
+     * size - {<OpenLayers.Size>} the size of the drawing surface
+     */
+    setSize: function(size) {
+        OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+
+        this.rendererRoot.style.width = this.size.w + "px";
+        this.rendererRoot.style.height = this.size.h + "px";
+
+        this.root.style.width = this.size.w + "px";
+        this.root.style.height = this.size.h + "px";
+    },
+
+    /**
+     * Method: getNodeType
+     * Get the node type for a geometry and style
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     *
+     * Returns:
+     * {String} The corresponding node type for the specified geometry
+     */
+    getNodeType: function(geometry, style) {
+        var nodeType = null;
+        switch (geometry.CLASS_NAME) {
+            case "OpenLayers.Geometry.Point":
+                if (style.externalGraphic) {
+                    nodeType = "olv:rect";
+                } else if (this.isComplexSymbol(style.graphicName)) {
+                    nodeType = "olv:shape";
+                } else {
+                    nodeType = "olv:oval";
+                }
+                break;
+            case "OpenLayers.Geometry.Rectangle":
+                nodeType = "olv:rect";
+                break;
+            case "OpenLayers.Geometry.LineString":
+            case "OpenLayers.Geometry.LinearRing":
+            case "OpenLayers.Geometry.Polygon":
+            case "OpenLayers.Geometry.Curve":
+            case "OpenLayers.Geometry.Surface":
+                nodeType = "olv:shape";
+                break;
+            default:
+                break;
+        }
+        return nodeType;
+    },
+
+    /**
+     * Method: setStyle
+     * Use to set all the style attributes to a VML node.
+     *
+     * Parameters:
+     * node - {DOMElement} An VML element to decorate
+     * style - {Object}
+     * options - {Object} Currently supported options include 
+     *                              'isFilled' {Boolean} and
+     *                              'isStroked' {Boolean}
+     * geometry - {<OpenLayers.Geometry>}
+     */
+    setStyle: function(node, style, options, geometry) {
+        style = style  || node._style;
+        options = options || node._options;
+        var widthFactor = 1;
+        
+        if (node._geometryClass == "OpenLayers.Geometry.Point") {
+            if (style.externalGraphic) {
+                var width = style.graphicWidth || style.graphicHeight;
+                var height = style.graphicHeight || style.graphicWidth;
+                width = width ? width : style.pointRadius*2;
+                height = height ? height : style.pointRadius*2;
+
+                var resolution = this.getResolution();
+                var xOffset = (style.graphicXOffset != undefined) ?
+                    style.graphicXOffset : -(0.5 * width);
+                var yOffset = (style.graphicYOffset != undefined) ?
+                    style.graphicYOffset : -(0.5 * height);
+                
+                node.style.left = ((geometry.x/resolution)+xOffset).toFixed();
+                node.style.top = ((geometry.y/resolution)-(yOffset+height)).toFixed();
+                node.style.width = width + "px";
+                node.style.height = height + "px";
+                node.style.flip = "y";
+                
+                // modify style/options for fill and stroke styling below
+                style.fillColor = "none";
+                options.isStroked = false;
+            } else if (this.isComplexSymbol(style.graphicName)) {
+                var cache = this.importSymbol(style.graphicName);
+                var symbolExtent = cache.extent;
+                var width = symbolExtent.getWidth();
+                var height = symbolExtent.getHeight();
+                node.setAttribute("path", cache.path);
+                node.setAttribute("coordorigin", symbolExtent.left + "," +
+                                                                symbolExtent.bottom);
+                node.setAttribute("coordsize", width + "," + height);
+                node.style.left = symbolExtent.left + "px";
+                node.style.top = symbolExtent.bottom + "px";
+                node.style.width = width + "px";
+                node.style.height = height + "px";
+        
+                this.drawCircle(node, geometry, style.pointRadius);
+                node.style.flip = "y";
+            } else {
+                this.drawCircle(node, geometry, style.pointRadius);
+            }
+        }
+
+        // fill 
+        if (options.isFilled) { 
+            node.setAttribute("fillcolor", style.fillColor); 
+        } else { 
+            node.setAttribute("filled", "false"); 
+        }
+        var fills = node.getElementsByTagName("fill");
+        var fill = (fills.length == 0) ? null : fills[0];
+        if (!options.isFilled) {
+            if (fill) {
+                node.removeChild(fill);
+            }
+        } else {
+            if (!fill) {
+                fill = this.createNode('olv:fill', node.id + "_fill");
+            }
+            fill.setAttribute("opacity", style.fillOpacity);
+
+            if (node._geometryClass == "OpenLayers.Geometry.Point" &&
+                    style.externalGraphic) {
+
+                // override fillOpacity
+                if (style.graphicOpacity) {
+                    fill.setAttribute("opacity", style.graphicOpacity);
+                }
+                
+                fill.setAttribute("src", style.externalGraphic);
+                fill.setAttribute("type", "frame");
+                
+                if (!(style.graphicWidth && style.graphicHeight)) {
+                  fill.aspect = "atmost";
+                }                
+            }
+            if (fill.parentNode != node) {
+                node.appendChild(fill);
+            }
+        }
+
+        // additional rendering for rotated graphics or symbols
+        if (typeof style.rotation != "undefined") {
+            if (style.externalGraphic) {
+                this.graphicRotate(node, xOffset, yOffset);
+                // make the fill fully transparent, because we now have
+                // the graphic as imagedata element. We cannot just remove
+                // the fill, because this is part of the hack described
+                // in graphicRotate
+                fill.setAttribute("opacity", 0);
+            } else {
+                node.style.rotation = style.rotation;
+            }
+        }
+
+        // stroke 
+        if (options.isStroked) { 
+            node.setAttribute("strokecolor", style.strokeColor); 
+            node.setAttribute("strokeweight", style.strokeWidth + "px"); 
+        } else { 
+            node.setAttribute("stroked", "false"); 
+        }
+        var strokes = node.getElementsByTagName("stroke");
+        var stroke = (strokes.length == 0) ? null : strokes[0];
+        if (!options.isStroked) {
+            if (stroke) {
+                node.removeChild(stroke);
+            }
+        } else {
+            if (!stroke) {
+                stroke = this.createNode('olv:stroke', node.id + "_stroke");
+                node.appendChild(stroke);
+            }
+            stroke.setAttribute("opacity", style.strokeOpacity);
+            stroke.setAttribute("endcap", !style.strokeLinecap || style.strokeLinecap == 'butt' ? 'flat' : style.strokeLinecap);
+            stroke.setAttribute("dashstyle", this.dashStyle(style));
+        }
+        
+        if (style.cursor != "inherit" && style.cursor != null) {
+            node.style.cursor = style.cursor;
+        }
+        return node;
+    },
+
+    /**
+     * Method: graphicRotate
+     * If a point is to be styled with externalGraphic and rotation, VML fills
+     * cannot be used to display the graphic, because rotation of graphic
+     * fills is not supported by the VML implementation of Internet Explorer.
+     * This method creates a olv:imagedata element inside the VML node,
+     * DXImageTransform.Matrix and BasicImage filters for rotation and
+     * opacity, and a 3-step hack to remove rendering artefacts from the
+     * graphic and preserve the ability of graphics to trigger events.
+     * Finally, OpenLayers methods are used to determine the correct
+     * insertion point of the rotated image, because DXImageTransform.Matrix
+     * does the rotation without the ability to specify a rotation center
+     * point.
+     * 
+     * Parameters:
+     * node    - {DOMElement}
+     * xOffset - {Number} rotation center relative to image, x coordinate
+     * yOffset - {Number} rotation center relative to image, y coordinate
+     */
+    graphicRotate: function(node, xOffset, yOffset) {
+        var style = style || node._style;
+        var options = node._options;
+        
+        var aspectRatio, size;
+        if (!(style.graphicWidth && style.graphicHeight)) {
+            // load the image to determine its size
+            var img = new Image();
+            img.onreadystatechange = OpenLayers.Function.bind(function() {
+                if(img.readyState == "complete" ||
+                        img.readyState == "interactive") {
+                    aspectRatio = img.width / img.height;
+                    size = Math.max(style.pointRadius * 2, 
+                        style.graphicWidth || 0,
+                        style.graphicHeight || 0);
+                    xOffset = xOffset * aspectRatio;
+                    style.graphicWidth = size * aspectRatio;
+                    style.graphicHeight = size;
+                    this.graphicRotate(node, xOffset, yOffset)
+                }
+            }, this);
+            img.src = style.externalGraphic;
+            
+            // will be called again by the onreadystate handler
+            return;
+        } else {
+            size = Math.max(style.graphicWidth, style.graphicHeight);
+            aspectRatio = style.graphicWidth / style.graphicHeight;
+        }
+        
+        var width = Math.round(style.graphicWidth || size * aspectRatio);
+        var height = Math.round(style.graphicHeight || size);
+        node.style.width = width + "px";
+        node.style.height = height + "px";
+        
+        // Three steps are required to remove artefacts for images with
+        // transparent backgrounds (resulting from using DXImageTransform
+        // filters on svg objects), while preserving awareness for browser
+        // events on images:
+        // - Use the fill as usual (like for unrotated images) to handle
+        //   events
+        // - specify an imagedata element with the same src as the fill
+        // - style the imagedata element with an AlphaImageLoader filter
+        //   with empty src
+        var image = document.getElementById(node.id + "_image");
+        if (!image) {
+            image = this.createNode("olv:imagedata", node.id + "_image");
+            node.appendChild(image);
+        }
+        image.style.width = width + "px";
+        image.style.height = height + "px";
+        image.src = style.externalGraphic;
+        image.style.filter =
+            "progid:DXImageTransform.Microsoft.AlphaImageLoader(" + 
+            "src='', sizingMethod='scale')";
+
+        var rotation = style.rotation * Math.PI / 180;
+        var sintheta = Math.sin(rotation);
+        var costheta = Math.cos(rotation);
+
+        // do the rotation on the image
+        var filter =
+            "progid:DXImageTransform.Microsoft.Matrix(M11=" + costheta +
+            ",M12=" + (-sintheta) + ",M21=" + sintheta + ",M22=" + costheta +
+            ",SizingMethod='auto expand')\n"
+
+        // set the opacity (needed for the imagedata)
+        var opacity = style.graphicOpacity || style.fillOpacity;
+        if (opacity && opacity != 1) {
+            filter += 
+                "progid:DXImageTransform.Microsoft.BasicImage(opacity=" + 
+                opacity+")\n";
+        }
+        node.style.filter = filter;
+
+        // do the rotation again on a box, so we know the insertion point
+        var centerPoint = new OpenLayers.Geometry.Point(-xOffset, -yOffset);
+        var imgBox = new OpenLayers.Bounds(0, 0, width, height).toGeometry();
+        imgBox.rotate(style.rotation, centerPoint);
+        var imgBounds = imgBox.getBounds();
+
+        node.style.left = Math.round(
+            parseInt(node.style.left) + imgBounds.left) + "px";
+        node.style.top = Math.round(
+            parseInt(node.style.top) - imgBounds.bottom) + "px";
+    },
+
+    /**
+     * Method: postDraw
+     * Some versions of Internet Explorer seem to be unable to set fillcolor
+     * and strokecolor to "none" correctly before the fill node is appended to
+     * a visible vml node. This method takes care of that and sets fillcolor
+     * and strokecolor again if needed.
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     */
+    postDraw: function(node) {
+        var fillColor = node._style.fillColor;
+        var strokeColor = node._style.strokeColor;
+        if (fillColor == "none" &&
+                node.getAttribute("fillcolor") != fillColor) {
+            node.setAttribute("fillcolor", fillColor);
+        }
+        if (strokeColor == "none" &&
+                node.getAttribute("strokecolor") != strokeColor) {
+            node.setAttribute("strokecolor", strokeColor);
+        }
+    },
+
+
+    /**
+     * Method: setNodeDimension
+     * Get the geometry's bounds, convert it to our vml coordinate system, 
+     * then set the node's position, size, and local coordinate system.
+     *   
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     */
+    setNodeDimension: function(node, geometry) {
+
+        var bbox = geometry.getBounds();
+        if(bbox) {
+            var resolution = this.getResolution();
+        
+            var scaledBox = 
+                new OpenLayers.Bounds((bbox.left/resolution).toFixed(),
+                                      (bbox.bottom/resolution).toFixed(),
+                                      (bbox.right/resolution).toFixed(),
+                                      (bbox.top/resolution).toFixed());
+            
+            // Set the internal coordinate system to draw the path
+            node.style.left = scaledBox.left + "px";
+            node.style.top = scaledBox.top + "px";
+            node.style.width = scaledBox.getWidth() + "px";
+            node.style.height = scaledBox.getHeight() + "px";
+    
+            node.coordorigin = scaledBox.left + " " + scaledBox.top;
+            node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight();
+        }
+    },
+    
+    /** 
+     * Method: dashStyle
+     * 
+     * Parameters:
+     * style - {Object}
+     * 
+     * Returns:
+     * {String} A VML compliant 'stroke-dasharray' value
+     */
+    dashStyle: function(style) {
+        var dash = style.strokeDashstyle;
+        switch (dash) {
+            case 'solid':
+            case 'dot':
+            case 'dash':
+            case 'dashdot':
+            case 'longdash':
+            case 'longdashdot':
+                return dash;
+            default:
+                // very basic guessing of dash style patterns
+                var parts = dash.split(/[ ,]/);
+                if (parts.length == 2) {
+                    if (1*parts[0] >= 2*parts[1]) {
+                        return "longdash";
+                    }
+                    return (parts[0] == 1 || parts[1] == 1) ? "dot" : "dash";
+                } else if (parts.length == 4) {
+                    return (1*parts[0] >= 2*parts[1]) ? "longdashdot" :
+                        "dashdot"
+                }
+                return "solid";
+        }
+    },
+
+    /**
+     * Method: createNode
+     * Create a new node
+     *
+     * Parameters:
+     * type - {String} Kind of node to draw
+     * id - {String} Id for node
+     *
+     * Returns:
+     * {DOMElement} A new node of the given type and id
+     */
+    createNode: function(type, id) {
+        var node = document.createElement(type);
+        if (id) {
+            node.setAttribute('id', id);
+        }
+        
+        // IE hack to make elements unselectable, to prevent 'blue flash'
+        // while dragging vectors; #1410
+        node.setAttribute('unselectable', 'on', 0);
+        node.onselectstart = function() { return(false); };
+        
+        return node;    
+    },
+    
+    /**
+     * Method: nodeTypeCompare
+     * Determine whether a node is of a given type
+     *
+     * Parameters:
+     * node - {DOMElement} An VML element
+     * type - {String} Kind of node
+     *
+     * Returns:
+     * {Boolean} Whether or not the specified node is of the specified type
+     */
+    nodeTypeCompare: function(node, type) {
+
+        //split type
+        var subType = type;
+        var splitIndex = subType.indexOf(":");
+        if (splitIndex != -1) {
+            subType = subType.substr(splitIndex+1);
+        }
+
+        //split nodeName
+        var nodeName = node.nodeName;
+        splitIndex = nodeName.indexOf(":");
+        if (splitIndex != -1) {
+            nodeName = nodeName.substr(splitIndex+1);
+        }
+
+        return (subType == nodeName);
+    },
+
+    /**
+     * Method: createRenderRoot
+     * Create the renderer root
+     *
+     * Returns:
+     * {DOMElement} The specific render engine's root element
+     */
+    createRenderRoot: function() {
+        return this.nodeFactory(this.container.id + "_vmlRoot", "div");
+    },
+
+    /**
+     * Method: createRoot
+     * Create the main root element
+     *
+     * Returns:
+     * {DOMElement} The main root element to which we'll add vectors
+     */
+    createRoot: function() {
+        return this.nodeFactory(this.container.id + "_root", "olv:group");
+    },
+    
+    /**************************************
+     *                                    *
+     *     GEOMETRY DRAWING FUNCTIONS     *
+     *                                    *
+     **************************************/
+    
+    /**
+     * Method: drawPoint
+     * Render a point
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the point could not be drawn
+     */
+    drawPoint: function(node, geometry) {
+        return this.drawCircle(node, geometry, 1);
+    },
+
+    /**
+     * Method: drawCircle
+     * Render a circle.
+     * Size and Center a circle given geometry (x,y center) and radius
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * radius - {float}
+     * 
+     * Returns:
+     * {DOMElement} or false if the circle could not ne drawn
+     */
+    drawCircle: function(node, geometry, radius) {
+        if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
+            var resolution = this.getResolution();
+        
+            node.style.left = ((geometry.x /resolution).toFixed() - radius) + "px";
+            node.style.top = ((geometry.y /resolution).toFixed() - radius) + "px";
+    
+            var diameter = radius * 2;
+            
+            node.style.width = diameter + "px";
+            node.style.height = diameter + "px";
+            return node;
+        }
+        return false;
+    },
+
+
+    /**
+     * Method: drawLineString
+     * Render a linestring.
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawLineString: function(node, geometry) {
+        return this.drawLine(node, geometry, false);
+    },
+
+    /**
+     * Method: drawLinearRing
+     * Render a linearring
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawLinearRing: function(node, geometry) {
+        return this.drawLine(node, geometry, true);
+    },
+
+    /**
+     * Method: DrawLine
+     * Render a line.
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * closeLine - {Boolean} Close the line? (make it a ring?)
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawLine: function(node, geometry, closeLine) {
+
+        this.setNodeDimension(node, geometry);
+
+        var resolution = this.getResolution();
+        var numComponents = geometry.components.length;
+        var parts = new Array(numComponents);
+
+        var comp, x, y;
+        for (var i = 0; i < numComponents; i++) {
+            comp = geometry.components[i];
+            x = (comp.x/resolution);
+            y = (comp.y/resolution);
+            parts[i] = " " + x.toFixed() + "," + y.toFixed() + " l ";
+        }
+        var end = (closeLine) ? " x e" : " e";
+        node.path = "m" + parts.join("") + end;
+        return node;
+    },
+
+    /**
+     * Method: drawPolygon
+     * Render a polygon
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawPolygon: function(node, geometry) {
+        this.setNodeDimension(node, geometry);
+
+        var resolution = this.getResolution();
+    
+        var path = [];
+        var linearRing, i, j, len, ilen, comp, x, y;
+        for (j = 0, len=geometry.components.length; j<len; j++) {
+            linearRing = geometry.components[j];
+
+            path.push("m");
+            for (i=0, ilen=linearRing.components.length; i<ilen; i++) {
+                comp = linearRing.components[i];
+                x = comp.x / resolution;
+                y = comp.y / resolution;
+                path.push(" " + x.toFixed() + "," + y.toFixed());
+                if (i==0) {
+                    path.push(" l");
+                }
+            }
+            path.push(" x ");
+        }
+        path.push("e");
+        node.path = path.join("");
+        return node;
+    },
+
+    /**
+     * Method: drawRectangle
+     * Render a rectangle
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawRectangle: function(node, geometry) {
+        var resolution = this.getResolution();
+    
+        node.style.left = geometry.x/resolution + "px";
+        node.style.top = geometry.y/resolution + "px";
+        node.style.width = geometry.width/resolution + "px";
+        node.style.height = geometry.height/resolution + "px";
+        
+        return node;
+    },
+
+    /**
+     * Method: drawSurface
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawSurface: function(node, geometry) {
+
+        this.setNodeDimension(node, geometry);
+
+        var resolution = this.getResolution();
+    
+        var path = [];
+        var comp, x, y;
+        for (var i=0, len=geometry.components.length; i<len; i++) {
+            comp = geometry.components[i];
+            x = comp.x / resolution;
+            y = comp.y / resolution;
+            if ((i%3)==0 && (i/3)==0) {
+                path.push("m");
+            } else if ((i%3)==1) {
+                path.push(" c");
+            }
+            path.push(" " + x + "," + y);
+        }
+        path.push(" x e");
+
+        node.path = path.join("");
+        return node;
+    },
+    
+    /**
+     * Method: importSymbol
+     * add a new symbol definition from the rendererer's symbol hash
+     * 
+     * Parameters:
+     * graphicName - {String} name of the symbol to import
+     * 
+     * Returns:
+     * {Object} - hash of {DOMElement} "symbol" and {Number} "size"
+     */      
+    importSymbol: function (graphicName)  {
+        var id = this.container.id + "-" + graphicName;
+        
+        // check if symbol already exists in the cache
+        var cache = this.symbolCache[id];
+        if (cache) {
+            return cache;
+        }
+        
+        var symbol = OpenLayers.Renderer.symbol[graphicName];
+        if (!symbol) {
+            throw new Error(graphicName + ' is not a valid symbol name');
+            return;
+        }
+
+        var symbolExtent = new OpenLayers.Bounds(
+                                    Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+        
+        var pathitems = ["m"];
+        for (var i=0; i<symbol.length; i=i+2) {
+            x = symbol[i];
+            y = symbol[i+1];
+            symbolExtent.left = Math.min(symbolExtent.left, x);
+            symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+            symbolExtent.right = Math.max(symbolExtent.right, x);
+            symbolExtent.top = Math.max(symbolExtent.top, y);
+
+            pathitems.push(x);
+            pathitems.push(y);
+            if (i == 0) {
+                pathitems.push("l");
+            }
+        }
+        pathitems.push("x e");
+        var path = pathitems.join(" ");
+        
+        cache = {
+            path: path,
+            extent: symbolExtent
+        };
+        this.symbolCache[id] = cache;
+        
+        return cache;
+    },
+    
+    CLASS_NAME: "OpenLayers.Renderer.VML"
+});
+/* ======================================================================
     OpenLayers/Tile/Image.js
    ====================================================================== */
 
@@ -10304,14 +20895,45 @@
      * the image will be hidden behind the frame. 
      */ 
     frame: null, 
-
     
     /**
      * Property: layerAlphaHack
      * {Boolean} True if the png alpha hack needs to be applied on the layer's div.
      */
     layerAlphaHack: null,
+    
+    /**
+     * Property: isBackBuffer
+     * {Boolean} Is this tile a back buffer tile?
+     */
+    isBackBuffer: false,
+    
+    /**
+     * Property: lastRatio
+     * {Float} Used in transition code only.  This is the previous ratio
+     *     of the back buffer tile resolution to the map resolution.  Compared
+     *     with the current ratio to determine if zooming occurred.
+     */
+    lastRatio: 1,
 
+    /**
+     * Property: isFirstDraw
+     * {Boolean} Is this the first time the tile is being drawn?
+     *     This is used to force resetBackBuffer to synchronize
+     *     the backBufferTile with the foreground tile the first time
+     *     the foreground tile loads so that if the user zooms
+     *     before the layer has fully loaded, the backBufferTile for
+     *     tiles that have been loaded can be used.
+     */
+    isFirstDraw: true,
+        
+    /**
+     * Property: backBufferTile
+     * {<OpenLayers.Tile>} A clone of the tile used to create transition
+     *     effects when the tile is moved or changes resolution.
+     */
+    backBufferTile: null,
+
     /** TBD 3.0 - reorder the parameters to the init function to remove 
      *             URL. the getUrl() function on the layer gets called on 
      *             each draw(), so no need to specify it here.
@@ -10353,12 +20975,22 @@
                 this.frame.removeChild(this.imgDiv);
                 this.imgDiv.map = null;
             }
+            this.imgDiv.urls = null;
         }
         this.imgDiv = null;
         if ((this.frame != null) && (this.frame.parentNode == this.layer.div)) { 
             this.layer.div.removeChild(this.frame); 
         }
         this.frame = null; 
+        
+        /* clean up the backBufferTile if it exists */
+        if (this.backBufferTile) {
+            this.backBufferTile.destroy();
+            this.backBufferTile = null;
+        }
+        
+        this.layer.events.unregister("loadend", this, this.resetBackBuffer);
+        
         OpenLayers.Tile.prototype.destroy.apply(this, arguments);
     },
 
@@ -10401,8 +21033,47 @@
         if (this.layer != this.layer.map.baseLayer && this.layer.reproject) {
             this.bounds = this.getBoundsFromBaseLayer(this.position);
         }
-        if (!OpenLayers.Tile.prototype.draw.apply(this, arguments)) {
-            return false;    
+        var drawTile = OpenLayers.Tile.prototype.draw.apply(this, arguments);
+        
+        if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, this.layer.transitionEffect) != -1) {
+            if (drawTile) {
+                //we use a clone of this tile to create a double buffer for visual
+                //continuity.  The backBufferTile is used to create transition
+                //effects while the tile in the grid is repositioned and redrawn
+                if (!this.backBufferTile) {
+                    this.backBufferTile = this.clone();
+                    this.backBufferTile.hide();
+                    // this is important.  It allows the backBuffer to place itself
+                    // appropriately in the DOM.  The Image subclass needs to put
+                    // the backBufferTile behind the main tile so the tiles can
+                    // load over top and display as soon as they are loaded.
+                    this.backBufferTile.isBackBuffer = true;
+                    
+                    // potentially end any transition effects when the tile loads
+                    this.events.register('loadend', this, this.resetBackBuffer);
+                    
+                    // clear transition back buffer tile only after all tiles in
+                    // this layer have loaded to avoid visual glitches
+                    this.layer.events.register("loadend", this, this.resetBackBuffer);
+                }
+                // run any transition effects
+                this.startTransition();
+            } else {
+                // if we aren't going to draw the tile, then the backBuffer should
+                // be hidden too!
+                if (this.backBufferTile) {
+                    this.backBufferTile.clear();
+                }
+            }
+        } else {
+            if (drawTile && this.isFirstDraw) {
+                this.events.register('loadend', this, this.showTile);
+                this.isFirstDraw = false;
+            }   
+        }    
+        
+        if (!drawTile) {
+            return false;
         }
         
         if (this.isLoading) {
@@ -10416,6 +21087,42 @@
         return this.renderTile();
     },
     
+    /** 
+     * Method: resetBackBuffer
+     * Triggered by two different events, layer loadend, and tile loadend.
+     *     In any of these cases, we check to see if we can hide the 
+     *     backBufferTile yet and update its parameters to match the 
+     *     foreground tile.
+     *
+     * Basic logic:
+     *  - If the backBufferTile hasn't been drawn yet, reset it
+     *  - If layer is still loading, show foreground tile but don't hide
+     *    the backBufferTile yet
+     *  - If layer is done loading, reset backBuffer tile and show 
+     *    foreground tile
+     */
+    resetBackBuffer: function() {
+        this.showTile();
+        if (this.backBufferTile && 
+            (this.isFirstDraw || !this.layer.numLoadingTiles)) {
+            this.isFirstDraw = false;
+            // check to see if the backBufferTile is within the max extents
+            // before rendering it 
+            var maxExtent = this.layer.maxExtent;
+            var withinMaxExtent = (maxExtent &&
+                                   this.bounds.intersectsBounds(maxExtent, false));
+            if (withinMaxExtent) {
+                this.backBufferTile.position = this.position;
+                this.backBufferTile.bounds = this.bounds;
+                this.backBufferTile.size = this.size;
+                this.backBufferTile.imageSize = this.layer.imageSize || this.size;
+                this.backBufferTile.imageOffset = this.layer.imageOffset;
+                this.backBufferTile.resolution = this.layer.getResolution();
+                this.backBufferTile.renderTile();
+            }
+        }
+    },
+    
     /**
      * Method: renderTile
      * Internal function to actually initialize the image tile,
@@ -10428,6 +21135,11 @@
 
         this.imgDiv.viewRequestID = this.layer.map.viewRequestID;
         
+        // needed for changing to a different serve for onload error
+        if (this.layer.url instanceof Array) {
+            this.imgDiv.urls = this.layer.url.slice();
+        }
+        
         this.url = this.layer.getURL(this.bounds);
         // position the frame 
         OpenLayers.Util.modifyDOMElement(this.frame, 
@@ -10685,6 +21397,201 @@
     OpenLayers.Util.getBrowserName() == "safari" || 
     OpenLayers.Util.getBrowserName() == "opera"); 
 /* ======================================================================
+    OpenLayers/Tile/WFS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+ 
+/**
+ * @requires OpenLayers/Tile.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Tile.WFS
+ * Instances of OpenLayers.Tile.WFS are used to manage the image tiles
+ * used by various layers.  Create a new image tile with the
+ * <OpenLayers.Tile.WFS> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Tile>
+ */
+OpenLayers.Tile.WFS = OpenLayers.Class(OpenLayers.Tile, {
+
+    /** 
+     * Property: features 
+     * {Array(<OpenLayers.Feature>)} list of features in this tile 
+     */
+    features: null,
+
+    /** 
+     * Property: url 
+     * {String} 
+     */
+    url: null,
+    
+    /** 
+     * Property: request 
+     * {<OpenLayers.Request.XMLHttpRequest>} 
+     */ 
+    request: null,     
+    
+    /** TBD 3.0 - reorder the parameters to the init function to put URL 
+     *             as last, so we can continue to call tile.initialize() 
+     *             without changing the arguments. 
+     * 
+     * Constructor: OpenLayers.Tile.WFS
+     * Constructor for a new <OpenLayers.Tile.WFS> instance.
+     * 
+     * Parameters:
+     * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+     * position - {<OpenLayers.Pixel>}
+     * bounds - {<OpenLayers.Bounds>}
+     * url - {<String>}
+     * size - {<OpenLayers.Size>}
+     */   
+    initialize: function(layer, position, bounds, url, size) {
+        OpenLayers.Tile.prototype.initialize.apply(this, arguments);
+        this.url = url;        
+        this.features = [];
+    },
+
+    /** 
+     * APIMethod: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+        OpenLayers.Tile.prototype.destroy.apply(this, arguments);
+        this.destroyAllFeatures();
+        this.features = null;
+        this.url = null;
+        if(this.request) {
+            this.request.abort();
+            //this.request.destroy();
+            this.request = null;
+        }
+    },
+
+    /** 
+     * Method: clear
+     *  Clear the tile of any bounds/position-related data so that it can 
+     *   be reused in a new location.
+     */
+    clear: function() {
+        this.destroyAllFeatures();
+    },
+    
+    /**
+     * Method: draw
+     * Check that a tile should be drawn, and load features for it.
+     */
+    draw:function() {
+        if (OpenLayers.Tile.prototype.draw.apply(this, arguments)) {
+            if (this.isLoading) {
+                //if already loading, send 'reload' instead of 'loadstart'.
+                this.events.triggerEvent("reload"); 
+            } else {
+                this.isLoading = true;
+                this.events.triggerEvent("loadstart");
+            }
+            this.loadFeaturesForRegion(this.requestSuccess);
+        }
+    },
+
+    /** 
+    * Method: loadFeaturesForRegion
+    * Abort any pending requests and issue another request for data. 
+    *
+    * Input are function pointers for what to do on success and failure.
+    *
+    * Parameters:
+    * success - {function}
+    * failure - {function}
+    */
+    loadFeaturesForRegion:function(success, failure) {
+        if(this.request) {
+            this.request.abort();
+        }
+        this.request = OpenLayers.Request.GET({
+            url: this.url,
+            success: success,
+            failure: failure,
+            scope: this
+        });
+    },
+    
+    /**
+    * Method: requestSuccess
+    * Called on return from request succcess. Adds results via 
+    * layer.addFeatures in vector mode, addResults otherwise. 
+    *
+    * Parameters:
+    * request - {<OpenLayers.Request.XMLHttpRequest>}
+    */
+    requestSuccess:function(request) {
+        if (this.features) {
+            var doc = request.responseXML;
+            if (!doc || !doc.documentElement) {
+                doc = request.responseText; 
+            }
+            if (this.layer.vectorMode) {
+                this.layer.addFeatures(this.layer.formatObject.read(doc));
+            } else {
+                var xml = new OpenLayers.Format.XML();
+                if (typeof doc == "string") {
+                    doc = xml.read(doc);
+                }
+                var resultFeatures = xml.getElementsByTagNameNS(
+                    doc, "http://www.opengis.net/gml", "featureMember"
+                );
+                this.addResults(resultFeatures);
+            }
+        }
+        if (this.events) {
+            this.events.triggerEvent("loadend"); 
+        }
+
+        //request produced with success, we can delete the request object.
+        //this.request.destroy();
+        this.request = null;
+    },
+
+    /**
+     * Method: addResults
+     * Construct new feature via layer featureClass constructor, and add to
+     * this.features.
+     * 
+     * Parameters:
+     * results - {Object}
+     */
+    addResults: function(results) {
+        for (var i=0; i < results.length; i++) {
+            var feature = new this.layer.featureClass(this.layer, 
+                                                      results[i]);
+            this.features.push(feature);
+        }
+    },
+
+
+    /** 
+     * Method: destroyAllFeatures
+     * Iterate through and call destroy() on each feature, removing it from
+     *   the local array
+     */
+    destroyAllFeatures: function() {
+        while(this.features.length > 0) {
+            var feature = this.features.shift();
+            feature.destroy();
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Tile.WFS"
+  }
+);
+/* ======================================================================
     OpenLayers/Control/OverviewMap.js
    ====================================================================== */
 
@@ -10778,7 +21685,7 @@
      * resolution at which to zoom farther in on the overview map.
      */
     maxRatio: 32,
-
+    
     /**
      * APIProperty: mapOptions
      * {Object} An object containing any non-default properties to be sent to
@@ -10794,6 +21701,12 @@
     handlers: null,
 
     /**
+     * Property: resolutionFactor
+     * {Object}
+     */
+    resolutionFactor: 1,
+
+    /**
      * Constructor: OpenLayers.Control.OverviewMap
      * Create a new overview map
      *
@@ -10930,7 +21843,7 @@
             
             var eventsToStop = ['dblclick','mousedown'];
             
-            for (var i = 0; i < eventsToStop.length; i++) {
+            for (var i=0, len=eventsToStop.length; i<len; i++) {
 
                 OpenLayers.Event.observe(this.maximizeDiv, 
                                          eventsToStop[i], 
@@ -11092,6 +22005,13 @@
                                 Math.max(mapExtent.bottom, maxExtent.bottom),
                                 Math.min(mapExtent.right, maxExtent.right),
                                 Math.min(mapExtent.top, maxExtent.top));        
+
+        if (this.ovmap.getProjection() != this.map.getProjection()) {
+            testExtent = testExtent.transform(
+                this.map.getProjectionObject(),
+                this.ovmap.getProjectionObject() );
+        }
+
         var resRatio = this.ovmap.getResolution() / this.map.getResolution();
         return ((resRatio > this.minRatio) &&
                 (resRatio <= this.maxRatio) &&
@@ -11113,8 +22033,16 @@
             // zoom out overview map
             targetRes = this.maxRatio * mapRes;
         }
-        this.ovmap.setCenter(this.map.center,
-                            this.ovmap.getZoomForResolution(targetRes));
+        var center;
+        if (this.ovmap.getProjection() != this.map.getProjection()) {
+            center = this.map.center.clone();
+            center.transform(this.map.getProjectionObject(),
+                this.ovmap.getProjectionObject() );
+        } else {
+            center = this.map.center;
+        }
+        this.ovmap.setCenter(center, this.ovmap.getZoomForResolution(
+            targetRes * this.resolutionFactor));
         this.updateRectToMap();
     },
     
@@ -11176,6 +22104,15 @@
             }
         });
 
+        if (this.ovmap.getProjection() != this.map.getProjection()) {
+            var sourceUnits = this.map.getProjectionObject().getUnits() ||
+                this.map.units || this.map.baseLayer.units;
+            var targetUnits = this.ovmap.getProjectionObject().getUnits() ||
+                this.ovmap.units || this.ovmap.baseLayer.units;
+            this.resolutionFactor = sourceUnits && targetUnits ?
+                OpenLayers.INCHES_PER_UNIT[sourceUnits] /
+                OpenLayers.INCHES_PER_UNIT[targetUnits] : 1;
+        }
     },
         
     /**
@@ -11183,14 +22120,16 @@
      * Updates the extent rectangle position and size to match the map extent
      */
     updateRectToMap: function() {
-        // The base layer for overview map needs to be in the same projection
-        // as the base layer for the main map.  This should be made more robust.
-        if(this.map.units != 'degrees') {
-            if(this.ovmap.getProjection() && (this.map.getProjection() != this.ovmap.getProjection())) {
-                alert(OpenLayers.i18n("sameProjection"));
-            }
+        // If the projections differ we need to reproject
+        var bounds;
+        if (this.ovmap.getProjection() != this.map.getProjection()) {
+            bounds = this.map.getExtent().transform(
+                this.map.getProjectionObject(), 
+                this.ovmap.getProjectionObject() );
+        } else {
+            bounds = this.map.getExtent();
         }
-        var pxBounds = this.getRectBoundsFromMapBounds(this.map.getExtent());
+        var pxBounds = this.getRectBoundsFromMapBounds(bounds);
         if (pxBounds) {
             this.setRectPxBounds(pxBounds);
         }
@@ -11202,6 +22141,11 @@
      */
     updateMapToRect: function() {
         var lonLatBounds = this.getMapBoundsFromRectBounds(this.rectPxBounds);
+        if (this.ovmap.getProjection() != this.map.getProjection()) {
+            lonLatBounds = lonLatBounds.transform(
+                this.ovmap.getProjectionObject(),
+                this.map.getProjectionObject() );
+        }
         this.map.panTo(lonLatBounds.getCenterLonLat());
     },
 
@@ -11341,6 +22285,1306 @@
     CLASS_NAME: 'OpenLayers.Control.OverviewMap'
 });
 /* ======================================================================
+    OpenLayers/Feature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Marker.js
+ * @requires OpenLayers/Popup/AnchoredBubble.js
+ */
+
+/**
+ * Class: OpenLayers.Feature
+ * Features are combinations of geography and attributes. The OpenLayers.Feature
+ *     class specifically combines a marker and a lonlat.
+ */
+OpenLayers.Feature = OpenLayers.Class({
+
+    /** 
+     * Property: layer 
+     * {<OpenLayers.Layer>} 
+     */
+    layer: null,
+
+    /** 
+     * Property: id 
+     * {String} 
+     */
+    id: null,
+    
+    /** 
+     * Property: lonlat 
+     * {<OpenLayers.LonLat>} 
+     */
+    lonlat: null,
+
+    /** 
+     * Property: data 
+     * {Object} 
+     */
+    data: null,
+
+    /** 
+     * Property: marker 
+     * {<OpenLayers.Marker>} 
+     */
+    marker: null,
+
+    /**
+     * APIProperty: popupClass
+     * {<OpenLayers.Class>} The class which will be used to instantiate
+     *     a new Popup. Default is <OpenLayers.Popup.AnchoredBubble>.
+     */
+    popupClass: OpenLayers.Popup.AnchoredBubble,
+
+    /** 
+     * Property: popup 
+     * {<OpenLayers.Popup>} 
+     */
+    popup: null,
+
+    /** 
+     * Constructor: OpenLayers.Feature
+     * Constructor for features.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer>} 
+     * lonlat - {<OpenLayers.LonLat>} 
+     * data - {Object} 
+     * 
+     * Returns:
+     * {<OpenLayers.Feature>}
+     */
+    initialize: function(layer, lonlat, data) {
+        this.layer = layer;
+        this.lonlat = lonlat;
+        this.data = (data != null) ? data : {};
+        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); 
+    },
+
+    /** 
+     * Method: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+
+        //remove the popup from the map
+        if ((this.layer != null) && (this.layer.map != null)) {
+            if (this.popup != null) {
+                this.layer.map.removePopup(this.popup);
+            }
+        }
+
+        this.layer = null;
+        this.id = null;
+        this.lonlat = null;
+        this.data = null;
+        if (this.marker != null) {
+            this.destroyMarker(this.marker);
+            this.marker = null;
+        }
+        if (this.popup != null) {
+            this.destroyPopup(this.popup);
+            this.popup = null;
+        }
+    },
+    
+    /**
+     * Method: onScreen
+     * 
+     * Returns:
+     * {Boolean} Whether or not the feature is currently visible on screen
+     *           (based on its 'lonlat' property)
+     */
+    onScreen:function() {
+        
+        var onScreen = false;
+        if ((this.layer != null) && (this.layer.map != null)) {
+            var screenBounds = this.layer.map.getExtent();
+            onScreen = screenBounds.containsLonLat(this.lonlat);
+        }    
+        return onScreen;
+    },
+    
+
+    /**
+     * Method: createMarker
+     * Based on the data associated with the Feature, create and return a marker object.
+     *
+     * Returns: 
+     * {<OpenLayers.Marker>} A Marker Object created from the 'lonlat' and 'icon' properties
+     *          set in this.data. If no 'lonlat' is set, returns null. If no
+     *          'icon' is set, OpenLayers.Marker() will load the default image.
+     *          
+     *          Note - this.marker is set to return value
+     * 
+     */
+    createMarker: function() {
+
+        if (this.lonlat != null) {
+            this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon);
+        }
+        return this.marker;
+    },
+
+    /**
+     * Method: destroyMarker
+     * Destroys marker.
+     * If user overrides the createMarker() function, s/he should be able
+     *   to also specify an alternative function for destroying it
+     */
+    destroyMarker: function() {
+        this.marker.destroy();  
+    },
+
+    /**
+     * Method: createPopup
+     * Creates a popup object created from the 'lonlat', 'popupSize',
+     *     and 'popupContentHTML' properties set in this.data. It uses
+     *     this.marker.icon as default anchor. 
+     *  
+     *  If no 'lonlat' is set, returns null. 
+     *  If no this.marker has been created, no anchor is sent.
+     *
+     *  Note - the returned popup object is 'owned' by the feature, so you
+     *      cannot use the popup's destroy method to discard the popup.
+     *      Instead, you must use the feature's destroyPopup
+     * 
+     *  Note - this.popup is set to return value
+     * 
+     * Parameters: 
+     * closeBox - {Boolean} create popup with closebox or not
+     * 
+     * Returns:
+     * {<OpenLayers.Popup>} Returns the created popup, which is also set
+     *     as 'popup' property of this feature. Will be of whatever type
+     *     specified by this feature's 'popupClass' property, but must be
+     *     of type <OpenLayers.Popup>.
+     * 
+     */
+    createPopup: function(closeBox) {
+
+        if (this.lonlat != null) {
+            
+            var id = this.id + "_popup";
+            var anchor = (this.marker) ? this.marker.icon : null;
+
+            if (!this.popup) {
+                this.popup = new this.popupClass(id, 
+                                                 this.lonlat,
+                                                 this.data.popupSize,
+                                                 this.data.popupContentHTML,
+                                                 anchor, 
+                                                 closeBox); 
+            }    
+            if (this.data.overflow != null) {
+                this.popup.contentDiv.style.overflow = this.data.overflow;
+            }    
+            
+            this.popup.feature = this;
+        }        
+        return this.popup;
+    },
+
+    
+    /**
+     * Method: destroyPopup
+     * Destroys the popup created via createPopup.
+     *
+     * As with the marker, if user overrides the createPopup() function, s/he 
+     *   should also be able to override the destruction
+     */
+    destroyPopup: function() {
+        if (this.popup) {
+            this.popup.feature = null;
+            this.popup.destroy();
+            this.popup = null;
+        }    
+    },
+
+    CLASS_NAME: "OpenLayers.Feature"
+});
+/* ======================================================================
+    OpenLayers/Format/WMC.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC
+ * Read and write Web Map Context documents.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WMC = OpenLayers.Class({
+    
+    /**
+     * APIProperty: defaultVersion
+     * {String} Version number to assume if none found.  Default is "1.1.0".
+     */
+    defaultVersion: "1.1.0",
+    
+    /**
+     * APIProperty: version
+     * {String} Specify a version string if one is known.
+     */
+    version: null,
+
+    /**
+     * Property: layerOptions
+     * {Object} Default options for layers created by the parser. These
+     *     options are overridden by the options which are read from the 
+     *     capabilities document.
+     */
+    layerOptions: null, 
+    
+    /**
+     * Property: parser
+     * {Object} Instance of the versioned parser.  Cached for multiple read and
+     *     write calls of the same version.
+     */
+    parser: null,
+
+    /**
+     * Constructor: OpenLayers.Format.WMC
+     * Create a new parser for WMC docs.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Util.extend(this, options);
+        this.options = options;
+    },
+
+    /**
+     * APIMethod: read
+     * Read WMC data from a string, and return an object with map properties
+     *     and a list of layers. 
+     * 
+     * Parameters: 
+     * data - {String} or {DOMElement} data to read/parse.
+     * options - {Object} The options object must contain a map property.  If
+     *     the map property is a string, it must be the id of a dom element
+     *     where the new map will be placed.  If the map property is an
+     *     <OpenLayers.Map>, the layers from the context document will be added
+     *     to the map.
+     *
+     * Returns:
+     * {<OpenLayers.Map>} A map based on the context.
+     */
+    read: function(data, options) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var root = data.documentElement;
+        var version = this.version;
+        if(!version) {
+            version = root.getAttribute("version");
+            if(!version) {
+                version = this.defaultVersion;
+            }
+        }
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.WMC[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a WMC parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        var context = this.parser.read(data, options);
+        var map;
+        if(options.map) {
+            this.context = context;
+            if(options.map instanceof OpenLayers.Map) {
+                map = this.mergeContextToMap(context, options.map);
+            } else {
+                map = this.contextToMap(context, options.map);
+            }
+        } else {
+            // not documented as part of the API, provided as a non-API option
+            map = context;
+        }
+        return map;
+    },
+    
+    /**
+     * Method: contextToMap
+     * Create a map given a context object.
+     *
+     * Parameters:
+     * context - {Object} The context object.
+     * id - {String | Element} The dom element or element id that will contain
+     *     the map.
+     *
+     * Returns:
+     * {<OpenLayers.Map>} A map based on the context object.
+     */
+    contextToMap: function(context, id) {
+        var map = new OpenLayers.Map(id, {
+            maxExtent: context.maxExtent,
+            projection: context.projection
+        });
+        map.addLayers(context.layers);
+        map.setCenter(
+            context.bounds.getCenterLonLat(),
+            map.getZoomForExtent(context.bounds, true)
+        );
+        return map;
+    },
+    
+    /**
+     * Method: mergeContextToMap
+     * Add layers from a context object to a map.
+     *
+     * Parameters:
+     * context - {Object} The context object.
+     * map - {<OpenLayers.Map>} The map.
+     *
+     * Returns:
+     * {<OpenLayers.Map>} The same map with layers added.
+     */
+    mergeContextToMap: function(context, map) {
+        map.addLayers(context.layers);
+        return map;
+    },
+
+    /**
+     * APIMethod: write
+     * Write a WMC document given a map.
+     *
+     * Parameters:
+     * obj - {<OpenLayers.Map> | Object} A map or context object.
+     * options - {Object} Optional configuration object.
+     *
+     * Returns:
+     * {String} A WMC document string.
+     */
+    write: function(obj, options) {
+        if(obj.CLASS_NAME == "OpenLayers.Map") {
+            obj = this.mapToContext(obj);
+        }
+        var version = (options && options.version) ||
+                      this.version || this.defaultVersion;
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.WMC[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a WMS capabilities parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        var wmc = this.parser.write(obj, options);
+        return wmc;
+    },
+    
+    /**
+     * Method: mapToContext
+     * Create a context object given a map.
+     *
+     * Parameters:
+     * map - {<OpenLayers.Map>} The map.
+     *
+     * Returns:
+     * {Object} A context object.
+     */
+    mapToContext: function(map) {
+        var context = {
+            bounds: map.getExtent(),
+            maxExtent: map.maxExtent,
+            projection: map.projection,
+            layers: map.layers,
+            size: map.getSize()
+        };
+        return context;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WMC" 
+
+});
+/* ======================================================================
+    OpenLayers/Format/WMC/v1.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1
+ * Superclass for WMC version 1 parsers.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WMC.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * Property: namespaces
+     * {Object} Mapping of namespace aliases to namespace URIs.
+     */
+    namespaces: {
+        ol: "http://openlayers.org/context",
+        wmc: "http://www.opengis.net/context",
+        sld: "http://www.opengis.net/sld",
+        xlink: "http://www.w3.org/1999/xlink",
+        xsi: "http://www.w3.org/2001/XMLSchema-instance"
+    },
+    
+    /**
+     * Property: schemaLocation
+     * {String} Schema location for a particular minor version.
+     */
+    schemaLocation: "",
+
+    /**
+     * Method: getNamespacePrefix
+     * Get the namespace prefix for a given uri from the <namespaces> object.
+     *
+     * Returns:
+     * {String} A namespace prefix or null if none found.
+     */
+    getNamespacePrefix: function(uri) {
+        var prefix = null;
+        if(uri == null) {
+            prefix = this.namespaces[this.defaultPrefix];
+        } else {
+            for(prefix in this.namespaces) {
+                if(this.namespaces[prefix] == uri) {
+                    break;
+                }
+            }
+        }
+        return prefix;
+    },
+    
+    /**
+     * Property: defaultPrefix
+     */
+    defaultPrefix: "wmc",
+
+    /**
+     * Property: rootPrefix
+     * {String} Prefix on the root node that maps to the context namespace URI.
+     */
+    rootPrefix: null,
+    
+    /**
+     * Property: defaultStyleName
+     * {String} Style name used if layer has no style param.  Default is "".
+     */
+    defaultStyleName: "",
+    
+    /**
+     * Property: defaultStyleTitle
+     * {String} Default style title.  Default is "Default".
+     */
+    defaultStyleTitle: "Default",
+    
+    /**
+     * Constructor: OpenLayers.Format.WMC.v1
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.WMC> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * Method: read
+     * Read capabilities data from a string, and return a list of layers. 
+     * 
+     * Parameters: 
+     * data - {String} or {DOMElement} data to read/parse.
+     *
+     * Returns:
+     * {Array} List of named layers.
+     */
+    read: function(data) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var root = data.documentElement;
+        this.rootPrefix = root.prefix;
+        var context = {
+            version: root.getAttribute("version")
+        };
+        this.runChildNodes(context, root);
+        return context;
+    },
+    
+    /**
+     * Method: runChildNodes
+     */
+    runChildNodes: function(obj, node) {
+        var children = node.childNodes;
+        var childNode, processor, prefix, local;
+        for(var i=0, len=children.length; i<len; ++i) {
+            childNode = children[i];
+            if(childNode.nodeType == 1) {
+                prefix = this.getNamespacePrefix(childNode.namespaceURI);
+                local = childNode.nodeName.split(":").pop();
+                processor = this["read_" + prefix + "_" + local];
+                if(processor) {
+                    processor.apply(this, [obj, childNode]);
+                }
+            }
+        }
+    },
+    
+    /**
+     * Method: read_wmc_General
+     */
+    read_wmc_General: function(context, node) {
+        this.runChildNodes(context, node);
+    },
+    
+    /**
+     * Method: read_wmc_BoundingBox
+     */
+    read_wmc_BoundingBox: function(context, node) {
+        context.projection = node.getAttribute("SRS");
+        context.bounds = new OpenLayers.Bounds(
+            parseFloat(node.getAttribute("minx")),
+            parseFloat(node.getAttribute("miny")),
+            parseFloat(node.getAttribute("maxx")),
+            parseFloat(node.getAttribute("maxy"))
+        );
+    },
+    
+    /**
+     * Method: read_wmc_LayerList
+     */
+    read_wmc_LayerList: function(context, node) {
+        context.layers = [];
+        this.runChildNodes(context, node);
+    },
+    
+    /**
+     * Method: read_wmc_Layer
+     */
+    read_wmc_Layer: function(context, node) {
+        var layerInfo = {
+            params: {},
+            options: {
+                visibility: (node.getAttribute("hidden") != "1"),
+                queryable: (node.getAttribute("queryable") == "1")
+                
+            },
+            formats: [],
+            styles: []
+        };
+        this.runChildNodes(layerInfo, node);
+        // set properties common to multiple objects on layer options/params
+        layerInfo.params.layers = layerInfo.name;
+        layerInfo.options.maxExtent = layerInfo.maxExtent;
+        // create the layer
+        var layer = this.getLayerFromInfo(layerInfo);
+        context.layers.push(layer);
+    },
+    
+    /**
+     * Method: getLayerFromInfo
+     * Create a WMS layer from a layerInfo object.
+     *
+     * Parameters:
+     * layerInfo - {Object} An object representing a WMS layer.
+     *
+     * Returns:
+     * {<OpenLayers.Layer.WMS>} A WMS layer.
+     */
+    getLayerFromInfo: function(layerInfo) {
+        var options = layerInfo.options;
+        if (this.layerOptions) {
+            OpenLayers.Util.applyDefaults(options, this.layerOptions);
+        }
+        var layer = new OpenLayers.Layer.WMS(
+            layerInfo.title,
+            layerInfo.href,
+            layerInfo.params,
+            options
+        );
+        return layer;
+    },
+    
+    /**
+     * Method: read_wmc_Extension
+     */
+    read_wmc_Extension: function(obj, node) {
+        this.runChildNodes(obj, node);
+    },
+
+    /**
+     * Method: read_ol_units
+     */
+    read_ol_units: function(layerInfo, node) {
+        layerInfo.options.units = this.getChildValue(node);
+    },
+    
+    /**
+     * Method: read_ol_maxExtent
+     */
+    read_ol_maxExtent: function(obj, node) {
+        var bounds = new OpenLayers.Bounds(
+            node.getAttribute("minx"), node.getAttribute("miny"),
+            node.getAttribute("maxx"), node.getAttribute("maxy")
+        );
+        obj.maxExtent = bounds;
+    },
+    
+    /**
+     * Method: read_ol_transparent
+     */
+    read_ol_transparent: function(layerInfo, node) {
+        layerInfo.params.transparent = this.getChildValue(node);
+    },
+
+    /**
+     * Method: read_ol_numZoomLevels
+     */
+    read_ol_numZoomLevels: function(layerInfo, node) {
+        layerInfo.options.numZoomLevels = parseInt(this.getChildValue(node));
+    },
+
+    /**
+     * Method: read_ol_opacity
+     */
+    read_ol_opacity: function(layerInfo, node) {
+        layerInfo.options.opacity = parseFloat(this.getChildValue(node));
+    },
+
+    /**
+     * Method: read_ol_singleTile
+     */
+    read_ol_singleTile: function(layerInfo, node) {
+        layerInfo.options.singleTile = (this.getChildValue(node) == "true");
+    },
+
+    /**
+     * Method: read_ol_isBaseLayer
+     */
+    read_ol_isBaseLayer: function(layerInfo, node) {
+        layerInfo.options.isBaseLayer = (this.getChildValue(node) == "true");
+    },
+
+    /**
+     * Method: read_ol_displayInLayerSwitcher
+     */
+    read_ol_displayInLayerSwitcher: function(layerInfo, node) {
+        layerInfo.options.displayInLayerSwitcher =
+            (this.getChildValue(node) == "true");
+    },
+
+    /**
+     * Method: read_wmc_Server
+     */
+    read_wmc_Server: function(layerInfo, node) {
+        layerInfo.params.version = node.getAttribute("version");
+        this.runChildNodes(layerInfo, node);
+    },
+
+    /**
+     * Method: read_wmc_FormatList
+     */
+    read_wmc_FormatList: function(layerInfo, node) {
+        this.runChildNodes(layerInfo, node);
+    },
+
+    /**
+     * Method: read_wmc_Format
+     */
+    read_wmc_Format: function(layerInfo, node) {
+        var format = this.getChildValue(node);
+        layerInfo.formats.push(format);
+        if(node.getAttribute("current") == "1") {
+            layerInfo.params.format = format;
+        }
+    },
+    
+    /**
+     * Method: read_wmc_StyleList
+     */
+    read_wmc_StyleList: function(layerInfo, node) {
+        this.runChildNodes(layerInfo, node);
+    },
+
+    /**
+     * Method: read_wmc_Style
+     */
+    read_wmc_Style: function(layerInfo, node) {
+        var style = {};
+        this.runChildNodes(style, node);
+        if(node.getAttribute("current") == "1") {
+            // three style types to consider
+            // 1) linked SLD
+            // 2) inline SLD
+            // 3) named style
+            // running child nodes always gets name, optionally gets href or body
+            if(style.href) {
+                layerInfo.params.sld = style.href;
+            } else if(style.body) {
+                layerInfo.params.sld_body = style.body;
+            } else {
+                layerInfo.params.styles = style.name;
+            }
+        }
+        layerInfo.styles.push(style);
+    },
+    
+    /**
+     * Method: read_wmc_SLD
+     */
+    read_wmc_SLD: function(style, node) {
+        this.runChildNodes(style, node);
+        // style either comes back with an href or a body property
+    },
+    
+    /**
+     * Method: read_sld_StyledLayerDescriptor
+     */
+    read_sld_StyledLayerDescriptor: function(sld, node) {
+        var xml = OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+        sld.body = xml;
+    },
+
+    /**
+     * Method: read_wmc_OnlineResource
+     */
+    read_wmc_OnlineResource: function(obj, node) {
+        obj.href = this.getAttributeNS(
+            node, this.namespaces.xlink, "href"
+        );
+    },
+    
+    /**
+     * Method: read_wmc_Name
+     */
+    read_wmc_Name: function(obj, node) {
+        var name = this.getChildValue(node);
+        if(name) {
+            obj.name = name;
+        }
+    },
+
+    /**
+     * Method: read_wmc_Title
+     */
+    read_wmc_Title: function(obj, node) {
+        var title = this.getChildValue(node);
+        if(title) {
+            obj.title = title;
+        }
+    },
+
+    /**
+     * Method: read_wmc_MetadataURL
+     */
+    read_wmc_MetadataURL: function(layerInfo, node) {
+        var metadataURL = {};
+        var links = node.getElementsByTagName("OnlineResource");
+        if(links.length > 0) {
+            this.read_wmc_OnlineResource(metadataURL, links[0]);
+        }
+        layerInfo.options.metadataURL = metadataURL.href;
+
+    },
+
+    /**
+     * Method: read_wmc_Abstract
+     */
+    read_wmc_Abstract: function(obj, node) {
+        var abst = this.getChildValue(node);
+        if(abst) {
+            obj["abstract"] = abst;
+        }
+    },
+    
+    /**
+     * Method: read_wmc_LatLonBoundingBox
+     */
+    read_wmc_LatLonBoundingBox: function(layer, node) {
+        layer.llbbox = [
+            parseFloat(node.getAttribute("minx")),
+            parseFloat(node.getAttribute("miny")),
+            parseFloat(node.getAttribute("maxx")),
+            parseFloat(node.getAttribute("maxy"))
+        ];
+    },
+
+    /**
+     * Method: read_wmc_LegendURL
+     */
+    read_wmc_LegendURL: function(style, node) {
+        var legend = {
+            width: node.getAttribute('width'),
+            height: node.getAttribute('height')
+        };
+        var links = node.getElementsByTagName("OnlineResource");
+        if(links.length > 0) {
+            this.read_wmc_OnlineResource(legend, links[0]);
+        }
+        style.legend = legend;
+    },
+    
+    /**
+     * Method: write
+     *
+     * Parameters:
+     * context - {Object} An object representing the map context.
+     * options - {Object} Optional object.
+     *
+     * Returns:
+     * {String} A WMC document string.
+     */
+    write: function(context, options) {
+        var root = this.createElementDefaultNS("ViewContext");
+        this.setAttributes(root, {
+            version: this.VERSION,
+            id: (options && typeof options.id == "string") ?
+                    options.id :
+                    OpenLayers.Util.createUniqueID("OpenLayers_Context_")
+        });
+        
+        // add schemaLocation attribute
+        this.setAttributeNS(
+            root, this.namespaces.xsi,
+            "xsi:schemaLocation", this.schemaLocation
+        );
+        
+        // required General element
+        root.appendChild(this.write_wmc_General(context));
+
+        // required LayerList element
+        root.appendChild(this.write_wmc_LayerList(context));
+
+        return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+    },
+    
+    /**
+     * Method: createElementDefaultNS
+     * Shorthand for createElementNS with namespace from <defaultPrefix>.
+     *     Can optionally be used to set attributes and a text child value.
+     *
+     * Parameters:
+     * name - {String} The qualified node name.
+     * childValue - {String} Optional value for text child node.
+     * attributes - {Object} Optional object representing attributes.
+     *
+     * Returns:
+     * {Element} An element node.
+     */
+    createElementDefaultNS: function(name, childValue, attributes) {
+        var node = this.createElementNS(
+            this.namespaces[this.defaultPrefix],
+            name
+        );
+        if(childValue) {
+            node.appendChild(this.createTextNode(childValue));
+        }
+        if(attributes) {
+            this.setAttributes(node, attributes);
+        }
+        return node;
+    },
+    
+    /**
+     * Method: setAttributes
+     * Set multiple attributes given key value pairs from an object.
+     *
+     * Parameters:
+     * node - {Element} An element node.
+     * obj - {Object} An object whose properties represent attribute names and
+     *     values represent attribute values.
+     */
+    setAttributes: function(node, obj) {
+        var value;
+        for(var name in obj) {
+            value = obj[name].toString();
+            if(value.match(/[A-Z]/)) {
+                // safari lowercases attributes with setAttribute
+                this.setAttributeNS(node, null, name, value);
+            } else {
+                node.setAttribute(name, value);
+            }
+        }
+    },
+
+    /**
+     * Method: write_wmc_General
+     * Create a General node given an context object.
+     *
+     * Parameters:
+     * context - {Object} Context object.
+     *
+     * Returns:
+     * {Element} A WMC General element node.
+     */
+    write_wmc_General: function(context) {
+        var node = this.createElementDefaultNS("General");
+
+        // optional Window element
+        if(context.size) {
+            node.appendChild(this.createElementDefaultNS(
+                "Window", null,
+                {
+                    width: context.size.w,
+                    height: context.size.h
+                }
+            ));
+        }
+        
+        // required BoundingBox element
+        var bounds = context.bounds;
+        node.appendChild(this.createElementDefaultNS(
+            "BoundingBox", null,
+            {
+                minx: bounds.left.toPrecision(10),
+                miny: bounds.bottom.toPrecision(10),
+                maxx: bounds.right.toPrecision(10),
+                maxy: bounds.top.toPrecision(10),
+                SRS: context.projection
+            }
+        ));
+
+        // required Title element
+        node.appendChild(this.createElementDefaultNS(
+            "Title", context.title
+        ));
+        
+        // OpenLayers specific map properties
+        node.appendChild(this.write_ol_MapExtension(context));
+        
+        return node;
+    },
+    
+    /**
+     * Method: write_ol_MapExtension
+     */
+    write_ol_MapExtension: function(context) {
+        var node = this.createElementDefaultNS("Extension");
+        
+        var bounds = context.maxExtent;
+        if(bounds) {
+            var maxExtent = this.createElementNS(
+                this.namespaces.ol, "ol:maxExtent"
+            );
+            this.setAttributes(maxExtent, {
+                minx: bounds.left.toPrecision(10),
+                miny: bounds.bottom.toPrecision(10),
+                maxx: bounds.right.toPrecision(10),
+                maxy: bounds.top.toPrecision(10)
+            });
+            node.appendChild(maxExtent);
+        }
+        
+        return node;
+    },
+    
+    /**
+     * Method: write_wmc_LayerList
+     * Create a LayerList node given an context object.
+     *
+     * Parameters:
+     * context - {Object} Context object.
+     *
+     * Returns:
+     * {Element} A WMC LayerList element node.
+     */
+    write_wmc_LayerList: function(context) {
+        var list = this.createElementDefaultNS("LayerList");
+        
+        var layer;
+        for(var i=0, len=context.layers.length; i<len; ++i) {
+            layer = context.layers[i];
+            if(layer instanceof OpenLayers.Layer.WMS) {
+                list.appendChild(this.write_wmc_Layer(layer));
+            }
+        }
+        
+        return list;
+    },
+
+    /**
+     * Method: write_wmc_Layer
+     * Create a Layer node given a layer object.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC Layer element node.
+     */
+    write_wmc_Layer: function(layer) {
+        var node = this.createElementDefaultNS(
+            "Layer", null, {
+                queryable: layer.queryable ? "1" : "0",
+                hidden: layer.visibility ? "0" : "1"
+            }
+        );
+        
+        // required Server element
+        node.appendChild(this.write_wmc_Server(layer));
+
+        // required Name element
+        node.appendChild(this.createElementDefaultNS(
+            "Name", layer.params["LAYERS"]
+        ));
+        
+        // required Title element
+        node.appendChild(this.createElementDefaultNS(
+            "Title", layer.name
+        ));
+
+        // optional MetadataURL element
+        if (layer.metadataURL) {
+            node.appendChild(this.write_wmc_MetadataURL(layer));
+        }
+        
+        // optional FormatList element
+        node.appendChild(this.write_wmc_FormatList(layer));
+
+        // optional StyleList element
+        node.appendChild(this.write_wmc_StyleList(layer));
+        
+        // OpenLayers specific properties go in an Extension element
+        node.appendChild(this.write_wmc_LayerExtension(layer));
+
+        return node;
+    },
+    
+    /**
+     * Method: write_wmc_LayerExtension
+     * Add OpenLayers specific layer parameters to an Extension element.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} A WMS layer.
+     *
+     * Returns:
+     * {Element} A WMC Extension element (for a layer).
+     */
+    write_wmc_LayerExtension: function(layer) {
+        var node = this.createElementDefaultNS("Extension");
+        
+        var bounds = layer.maxExtent;
+        var maxExtent = this.createElementNS(
+            this.namespaces.ol, "ol:maxExtent"
+        );
+        this.setAttributes(maxExtent, {
+            minx: bounds.left.toPrecision(10),
+            miny: bounds.bottom.toPrecision(10),
+            maxx: bounds.right.toPrecision(10),
+            maxy: bounds.top.toPrecision(10)
+        });
+        node.appendChild(maxExtent);
+        
+        var param = layer.params["TRANSPARENT"];
+        if(param) {
+            var trans = this.createElementNS(
+                this.namespaces.ol, "ol:transparent"
+            );
+            trans.appendChild(this.createTextNode(param));
+            node.appendChild(trans);
+        }
+        
+        var properties = [
+            "numZoomLevels", "units", "isBaseLayer",
+            "opacity", "displayInLayerSwitcher", "singleTile"
+        ];
+        var child;
+        for(var i=0, len=properties.length; i<len; ++i) {
+            child = this.createOLPropertyNode(layer, properties[i]);
+            if(child) {
+                node.appendChild(child);
+            }
+        }
+
+        return node;
+    },
+    
+    /**
+     * Method: createOLPropertyNode
+     * Create a node representing an OpenLayers property.  If the property is
+     *     null or undefined, null will be returned.
+     *
+     * Parameters:
+     * object - {Object} An object.
+     * prop - {String} A property.
+     *
+     * Returns:
+     * {Element} A property node.
+     */
+    createOLPropertyNode: function(obj, prop) {
+        var node = null;
+        if(obj[prop] != null) {
+            node = this.createElementNS(this.namespaces.ol, "ol:" + prop);
+            node.appendChild(this.createTextNode(obj[prop].toString()));
+        }
+        return node;
+    },
+
+    /**
+     * Method: write_wmc_Server
+     * Create a Server node given a layer object.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC Server element node.
+     */
+    write_wmc_Server: function(layer) {
+        var node = this.createElementDefaultNS("Server");
+        this.setAttributes(node, {
+            service: "OGC:WMS",
+            version: layer.params["VERSION"]
+        });
+        
+        // required OnlineResource element
+        node.appendChild(this.write_wmc_OnlineResource(layer.url));
+        
+        return node;
+    },
+
+    /**
+     * Method: write_wmc_MetadataURL
+     * Create a MetadataURL node given a layer object.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC metadataURL element node.
+     */
+    write_wmc_MetadataURL: function(layer) {
+        var node = this.createElementDefaultNS("MetadataURL");
+
+        // required OnlineResource element
+        node.appendChild(this.write_wmc_OnlineResource(layer.metadataURL));
+
+        return node;
+    },
+
+    /**
+     * Method: write_wmc_FormatList
+     * Create a FormatList node given a layer.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC FormatList element node.
+     */
+    write_wmc_FormatList: function(layer) {
+        var node = this.createElementDefaultNS("FormatList");
+        node.appendChild(this.createElementDefaultNS(
+            "Format", layer.params["FORMAT"], {current: "1"}
+        ));
+
+        return node;
+    },
+
+    /**
+     * Method: write_wmc_StyleList
+     * Create a StyleList node given a layer.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC StyleList element node.
+     */
+    write_wmc_StyleList: function(layer) {
+        var node = this.createElementDefaultNS("StyleList");
+        var style = this.createElementDefaultNS(
+            "Style", null, {current: "1"}
+        );
+        
+        // Style can come from one of three places (prioritized as below):
+        // 1) an SLD parameter
+        // 2) and SLD_BODY parameter
+        // 3) the STYLES parameter
+        
+        if(layer.params["SLD"]) {
+            // create link from SLD parameter
+            var sld = this.createElementDefaultNS("SLD");
+            var link = this.write_wmc_OnlineResource(layer.params["SLD"]);
+            sld.appendChild(link);
+            style.appendChild(sld);
+        } else if(layer.params["SLD_BODY"]) {
+            // include sld fragment from SLD_BODY parameter
+            var sld = this.createElementDefaultNS("SLD");
+            var body = layer.params["SLD_BODY"];
+            // read in body as xml doc - assume proper namespace declarations
+            var doc = OpenLayers.Format.XML.prototype.read.apply(this, [body]);
+            // append to StyledLayerDescriptor node
+            var imported = doc.documentElement;
+            if(sld.ownerDocument && sld.ownerDocument.importNode) {
+                imported = sld.ownerDocument.importNode(imported, true);
+            }
+            sld.appendChild(imported);
+            style.appendChild(sld);            
+        } else {
+            // use name(s) from STYLES parameter
+            var name = layer.params["STYLES"] ?
+                layer.params["STYLES"] : this.defaultStyleName;
+            
+            style.appendChild(this.createElementDefaultNS("Name", name));
+            style.appendChild(this.createElementDefaultNS(
+                "Title", this.defaultStyleTitle
+            ));
+        }
+        node.appendChild(style);
+        return node;
+    },
+
+    /**
+     * Method: write_wmc_OnlineResource
+     * Create an OnlineResource node given a URL.
+     *
+     * Parameters:
+     * href - {String} URL for the resource.
+     *
+     * Returns:
+     * {Element} A WMC OnlineResource element node.
+     */
+    write_wmc_OnlineResource: function(href) {
+        var node = this.createElementDefaultNS("OnlineResource");
+        this.setAttributeNS(node, this.namespaces.xlink, "xlink:type", "simple");
+        this.setAttributeNS(node, this.namespaces.xlink, "xlink:href", href);
+        return node;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WMC.v1" 
+
+});
+/* ======================================================================
     OpenLayers/Handler/Click.js
    ====================================================================== */
 
@@ -11436,6 +23680,13 @@
     down: null,
     
     /**
+     * Property: rightclickTimerId
+     * {Number} The id of the right mouse timeout waiting to clear the 
+     *     <delayedEvent>.
+     */
+    rightclickTimerId: null,
+    
+    /**
      * Constructor: OpenLayers.Handler.Click
      * Create a new click handler.
      * 
@@ -11471,8 +23722,80 @@
      * {Boolean} Continue propagating this event.
      */
     mousedown: null,
+
+    /**
+     * Method: mouseup
+     * Handle mouseup.  Installed to support collection of right mouse events.
+     * 
+     * Returns:
+     * {Boolean} Continue propagating this event.
+     */
+    mouseup:  function (evt) {
+        var propagate = true;
+
+        // Collect right mouse clicks from the mouseup
+        //  IE - ignores the second right click in mousedown so using
+        //  mouseup instead
+        if (this.checkModifiers(evt) && 
+            this.control.handleRightClicks && 
+            OpenLayers.Event.isRightClick(evt)) {
+          propogate = this.rightclick(evt);
+        }
+
+        return propagate;
+    },
     
     /**
+     * Method: rightclick
+     * Handle rightclick.  For a dblrightclick, we get two clicks so we need 
+     *     to always register for dblrightclick to properly handle single 
+     *     clicks.
+     *     
+     * Returns:
+     * {Boolean} Continue propagating this event.
+     */
+    rightclick: function(evt) {
+        if(this.passesTolerance(evt)) {
+           if(this.rightclickTimerId != null) {
+                //Second click received before timeout this must be 
+                // a double click
+                this.clearTimer();      
+                this.callback('dblrightclick', [evt]);
+                return !this.stopDouble;
+            } else { 
+                //Set the rightclickTimerId, send evt only if double is 
+                // true else trigger single
+                var clickEvent = this['double'] ?
+                    OpenLayers.Util.extend({}, evt) : 
+                    this.callback('rightclick', [evt]);
+
+                var delayedRightCall = OpenLayers.Function.bind(
+                    this.delayedRightCall, 
+                    this, 
+                    clickEvent
+                );
+                this.rightclickTimerId = window.setTimeout(
+                    delayedRightCall, this.delay
+                );
+            } 
+        }
+        return !this.stopSingle;
+    },
+    
+    /**
+     * Method: delayedRightCall
+     * Sets <rightclickTimerId> to null.  And optionally triggers the 
+     *     rightclick callback if evt is set.
+     */
+    delayedRightCall: function(evt) {
+        this.rightclickTimerId = null;
+        if (evt) {
+           this.callback('rightclick', [evt]);
+        }
+        return !this.stopSingle;
+    },
+    
+    /**
      * Method: dblclick
      * Handle dblclick.  For a dblclick, we get two clicks in some browsers
      *     (FF) and one in others (IE).  So we need to always register for
@@ -11657,6 +23980,23 @@
      * {Function}
      */
     oldOnselectstart: null,
+    
+    /**
+     * Property: interval
+     * {Integer} In order to increase performance, an interval (in 
+     *     milliseconds) can be set to reduce the number of drag events 
+     *     called. If set, a new drag event will not be set until the 
+     *     interval has passed. 
+     *     Defaults to 0, meaning no interval. 
+     */
+    interval: 0,
+    
+    /**
+     * Property: timeoutId
+     * {String} The id of the timeout used for the mousedown interval.
+     *     This is "private", and should be left alone.
+     */
+    timeoutId: null,
 
     /**
      * Constructor: OpenLayers.Handler.Drag
@@ -11782,20 +24122,29 @@
      * {Boolean} Let the event propagate.
      */
     mousemove: function (evt) {
-        if (this.started) {
-            if(evt.xy.x != this.last.x || evt.xy.y != this.last.y) {
-                this.dragging = true;
-                this.move(evt);
-                this.callback("move", [evt.xy]);
-                if(!this.oldOnselectstart) {
-                    this.oldOnselectstart = document.onselectstart;
-                    document.onselectstart = function() {return false;};
-                }
-                this.last = evt.xy;
+        if (this.started && !this.timeoutId && (evt.xy.x != this.last.x || evt.xy.y != this.last.y)) {
+            if (this.interval > 0) {
+                this.timeoutId = setTimeout(OpenLayers.Function.bind(this.removeTimeout, this), this.interval);
             }
+            this.dragging = true;
+            this.move(evt);
+            this.callback("move", [evt.xy]);
+            if(!this.oldOnselectstart) {
+                this.oldOnselectstart = document.onselectstart;
+                document.onselectstart = function() {return false;};
+            }
+            this.last = this.evt.xy;
         }
         return true;
     },
+    
+    /**
+     * Method: removeTimeout
+     * Private. Called by mousemove() to remove the drag timeout.
+     */
+    removeTimeout: function() {
+        this.timeoutId = null;
+    },
 
     /**
      * Method: mouseup
@@ -11908,6 +24257,680 @@
     CLASS_NAME: "OpenLayers.Handler.Drag"
 });
 /* ======================================================================
+    OpenLayers/Handler/Feature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Feature 
+ * Handler to respond to mouse events related to a drawn feature.  Callbacks
+ *     with the following keys will be notified of the following events
+ *     associated with features: click, clickout, over, out, and dblclick.
+ *
+ * This handler stops event propagation for mousedown and mouseup if those
+ *     browser events target features that can be selected.
+ */
+OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
+
+    /**
+     * Property: EVENTMAP
+     * {Object} A object mapping the browser events to objects with callback
+     *     keys for in and out.
+     */
+    EVENTMAP: {
+        'click': {'in': 'click', 'out': 'clickout'},
+        'mousemove': {'in': 'over', 'out': 'out'},
+        'dblclick': {'in': 'dblclick', 'out': null},
+        'mousedown': {'in': null, 'out': null},
+        'mouseup': {'in': null, 'out': null}
+    },
+
+    /**
+     * Property: feature
+     * {<OpenLayers.Feature.Vector>} The last feature that was hovered.
+     */
+    feature: null,
+
+    /**
+     * Property: lastFeature
+     * {<OpenLayers.Feature.Vector>} The last feature that was handled.
+     */
+    lastFeature: null,
+
+    /**
+     * Property: down
+     * {<OpenLayers.Pixel>} The location of the last mousedown.
+     */
+    down: null,
+
+    /**
+     * Property: up
+     * {<OpenLayers.Pixel>} The location of the last mouseup.
+     */
+    up: null,
+    
+    /**
+     * Property: clickoutTolerance
+     * {Number} The number of pixels the mouse can move during a click that
+     *     still constitutes a click out.  When dragging the map, clicks should
+     *     not trigger the clickout property unless this tolerance is reached.
+     *     Default is 4.
+     */
+    clickoutTolerance: 4,
+
+    /**
+     * Property: geometryTypes
+     * To restrict dragging to a limited set of geometry types, send a list
+     * of strings corresponding to the geometry class names.
+     * 
+     * @type Array(String)
+     */
+    geometryTypes: null,
+
+    /**
+     * Property: stopClick
+     * {Boolean} If stopClick is set to true, handled clicks do not
+     *      propagate to other click listeners. Otherwise, handled clicks
+     *      do propagate. Unhandled clicks always propagate, whatever the
+     *      value of stopClick. Defaults to true.
+     */
+    stopClick: true,
+
+    /**
+     * Property: stopDown
+     * {Boolean} If stopDown is set to true, handled mousedowns do not
+     *      propagate to other mousedown listeners. Otherwise, handled
+     *      mousedowns do propagate. Unhandled mousedowns always propagate,
+     *      whatever the value of stopDown. Defaults to true.
+     */
+    stopDown: true,
+
+    /**
+     * Property: stopUp
+     * {Boolean} If stopUp is set to true, handled mouseups do not
+     *      propagate to other mouseup listeners. Otherwise, handled mouseups
+     *      do propagate. Unhandled mouseups always propagate, whatever the
+     *      value of stopUp. Defaults to false.
+     */
+    stopUp: false,
+    
+    /**
+     * Constructor: OpenLayers.Handler.Feature
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} 
+     * layers - {Array(<OpenLayers.Layer.Vector>)}
+     * callbacks - {Object} An object with a 'over' property whos value is
+     *     a function to be called when the mouse is over a feature. The 
+     *     callback should expect to recieve a single argument, the feature.
+     * options - {Object} 
+     */
+    initialize: function(control, layer, callbacks, options) {
+        OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
+        this.layer = layer;
+    },
+
+
+    /**
+     * Method: mousedown
+     * Handle mouse down.  Stop propagation if a feature is targeted by this
+     *     event (stops map dragging during feature selection).
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    mousedown: function(evt) {
+        this.down = evt.xy;
+        return this.handle(evt) ? !this.stopDown : true;
+    },
+    
+    /**
+     * Method: mouseup
+     * Handle mouse up.  Stop propagation if a feature is targeted by this
+     *     event.
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    mouseup: function(evt) {
+        this.up = evt.xy;
+        return this.handle(evt) ? !this.stopUp : true;
+    },
+
+    /**
+     * Method: click
+     * Handle click.  Call the "click" callback if click on a feature,
+     *     or the "clickout" callback if click outside any feature.
+     * 
+     * Parameters:
+     * evt - {Event} 
+     *
+     * Returns:
+     * {Boolean}
+     */
+    click: function(evt) {
+        return this.handle(evt) ? !this.stopClick : true;
+    },
+        
+    /**
+     * Method: mousemove
+     * Handle mouse moves.  Call the "over" callback if moving in to a feature,
+     *     or the "out" callback if moving out of a feature.
+     * 
+     * Parameters:
+     * evt - {Event} 
+     *
+     * Returns:
+     * {Boolean}
+     */
+    mousemove: function(evt) {
+        if (!this.callbacks['over'] && !this.callbacks['out']) {
+            return true;
+        }     
+        this.handle(evt);
+        return true;
+    },
+    
+    /**
+     * Method: dblclick
+     * Handle dblclick.  Call the "dblclick" callback if dblclick on a feature.
+     *
+     * Parameters:
+     * evt - {Event} 
+     *
+     * Returns:
+     * {Boolean}
+     */
+    dblclick: function(evt) {
+        return !this.handle(evt);
+    },
+
+    /**
+     * Method: geometryTypeMatches
+     * Return true if the geometry type of the passed feature matches
+     *     one of the geometry types in the geometryTypes array.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Vector.Feature>}
+     *
+     * Returns:
+     * {Boolean}
+     */
+    geometryTypeMatches: function(feature) {
+        return this.geometryTypes == null ||
+            OpenLayers.Util.indexOf(this.geometryTypes,
+                                    feature.geometry.CLASS_NAME) > -1;
+    },
+
+    /**
+     * Method: handle
+     *
+     * Parameters:
+     * evt - {Event}
+     *
+     * Returns:
+     * {Boolean} The event occurred over a relevant feature.
+     */
+    handle: function(evt) {
+        var type = evt.type;
+        var handled = false;
+        var previouslyIn = !!(this.feature); // previously in a feature
+        var click = (type == "click" || type == "dblclick");
+        this.feature = this.layer.getFeatureFromEvent(evt);
+        if(this.feature) {
+            var inNew = (this.feature != this.lastFeature);
+            if(this.geometryTypeMatches(this.feature)) {
+                // in to a feature
+                if(previouslyIn && inNew) {
+                    // out of last feature and in to another
+                    this.triggerCallback(type, 'out', [this.lastFeature]);
+                    this.triggerCallback(type, 'in', [this.feature]);
+                } else if(!previouslyIn || click) {
+                    // in feature for the first time
+                    this.triggerCallback(type, 'in', [this.feature]);
+                }
+                this.lastFeature = this.feature;
+                handled = true;
+            } else {
+                // not in to a feature
+                if(previouslyIn && inNew || (click && this.lastFeature)) {
+                    // out of last feature for the first time
+                    this.triggerCallback(type, 'out', [this.lastFeature]);
+                }
+                // next time the mouse goes in a feature whose geometry type
+                // doesn't match we don't want to call the 'out' callback
+                // again, so let's set this.feature to null so that
+                // previouslyIn will evaluate to false the next time
+                // we enter handle. Yes, a bit hackish...
+                this.feature = null;
+            }
+        } else {
+            if(previouslyIn || (click && this.lastFeature)) {
+                this.triggerCallback(type, 'out', [this.lastFeature]);
+            }
+        }
+        return handled;
+    },
+    
+    /**
+     * Method: triggerCallback
+     * Call the callback keyed in the event map with the supplied arguments.
+     *     For click out, the <clickoutTolerance> is checked first.
+     *
+     * Parameters:
+     * type - {String}
+     */
+    triggerCallback: function(type, mode, args) {
+        var key = this.EVENTMAP[type][mode];
+        if(key) {
+            if(type == 'click' && mode == 'out' && this.up && this.down) {
+                // for clickout, only trigger callback if tolerance is met
+                var dpx = Math.sqrt(
+                    Math.pow(this.up.x - this.down.x, 2) +
+                    Math.pow(this.up.y - this.down.y, 2)
+                );
+                if(dpx <= this.clickoutTolerance) {
+                    this.callback(key, args);
+                }
+            } else {
+                this.callback(key, args);
+            }
+        }
+    },
+
+    /**
+     * Method: activate 
+     * Turn on the handler.  Returns false if the handler was already active.
+     *
+     * Returns:
+     * {Boolean}
+     */
+    activate: function() {
+        var activated = false;
+        if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+            this.moveLayerToTop();
+            this.map.events.on({
+                "removelayer": this.handleMapEvents,
+                "changelayer": this.handleMapEvents,
+                scope: this
+            });
+            activated = true;
+        }
+        return activated;
+    },
+    
+    /**
+     * Method: deactivate 
+     * Turn off the handler.  Returns false if the handler was already active.
+     *
+     * Returns: 
+     * {Boolean}
+     */
+    deactivate: function() {
+        var deactivated = false;
+        if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+            this.moveLayerBack();
+            this.feature = null;
+            this.lastFeature = null;
+            this.down = null;
+            this.up = null;
+            this.map.events.un({
+                "removelayer": this.handleMapEvents,
+                "changelayer": this.handleMapEvents,
+                scope: this
+            });
+            deactivated = true;
+        }
+        return deactivated;
+    },
+    
+    /**
+     * Method handleMapEvents
+     * 
+     * Parameters:
+     * evt - {Object}
+     */
+    handleMapEvents: function(evt) {
+        if (!evt.property || evt.property == "order") {
+            this.moveLayerToTop();
+        }
+    },
+    
+    /**
+     * Method: moveLayerToTop
+     * Moves the layer for this handler to the top, so mouse events can reach
+     * it.
+     */
+    moveLayerToTop: function() {
+        var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
+            this.layer.getZIndex()) + 1;
+        this.layer.setZIndex(index);
+        
+    },
+    
+    /**
+     * Method: moveLayerBack
+     * Moves the layer back to the position determined by the map's layers
+     * array.
+     */
+    moveLayerBack: function() {
+        var index = this.layer.getZIndex() - 1;
+        if (index >= this.map.Z_INDEX_BASE['Feature']) {
+            this.layer.setZIndex(index);
+        } else {
+            this.map.setLayerZIndex(this.layer,
+                this.map.getLayerIndex(this.layer));
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Feature"
+});
+/* ======================================================================
+    OpenLayers/Handler/Hover.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the clear BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Hover
+ * The hover handler is to be used to emulate mouseovers on objects
+ *      on the map that aren't DOM elements. For example one can use
+ *      this handler to send WMS/GetFeatureInfo requests as the user
+ *      moves the mouve over the map.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Handler> 
+ */
+OpenLayers.Handler.Hover = OpenLayers.Class(OpenLayers.Handler, {
+
+    /**
+     * APIProperty: delay
+     * {Integer} - Number of milliseconds between mousemoves before
+     *      the event is considered a hover. Default is 500.
+     */
+    delay: 500,
+    
+    /**
+     * APIProperty: pixelTolerance
+     * {Integer} - Maximum number of pixels between mousemoves for
+     *      an event to be considered a hover. Default is null.
+     */
+    pixelTolerance: null,
+
+    /**
+     * APIProperty: stopMove
+     * {Boolean} Stop other listeners from being notified on mousemoves.
+     *      Default is false.
+     */
+    stopMove: false,
+
+    /**
+     * Property: px
+     * {<OpenLayers.Pixel>} The location of the last mousemove, expressed
+     *      in pixels.
+     */
+    px: null,
+
+    /**
+     * Property: timerId
+     * {Number} The id of the timer.
+     */
+    timerId: null,
+ 
+    /**
+     * Constructor: OpenLayers.Handler.Hover
+     * Construct a hover handler.
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} The control that initialized this
+     *     handler.  The control is assumed to have a valid map property; that
+     *     map is used in the handler's own setMap method.
+     * callbacks - {Object} An object whose properties correspond to abstracted
+     *     events or sequences of browser events.  The values for these
+     *     properties are functions defined by the control that get called by
+     *     the handler.
+     * options - {Object} An optional object whose properties will be set on
+     *     the handler.
+     */
+    initialize: function(control, callbacks, options) {
+        OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+    },
+
+    /**
+     * Method: mousemove
+     * Called when the mouse moves on the map.
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>}
+     *
+     * Returns:
+     * {Boolean} Continue propagating this event.
+     */
+    mousemove: function(evt) {
+        if(this.passesTolerance(evt.xy)) {
+            this.clearTimer();
+            this.callback('move', [evt]);
+            this.px = evt.xy;
+            // clone the evt so original properties can be accessed even
+            // if the browser deletes them during the delay
+            evt = OpenLayers.Util.extend({}, evt);
+            this.timerId = window.setTimeout(
+                OpenLayers.Function.bind(this.delayedCall, this, evt),
+                this.delay
+            );
+        }
+        return !this.stopMove;
+    },
+
+    /**
+     * Method: mouseout
+     * Called when the mouse goes out of the map.
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>}
+     *
+     * Returns:
+     * {Boolean} Continue propagating this event.
+     */
+    mouseout: function(evt) {
+        if (OpenLayers.Util.mouseLeft(evt, this.map.div)) {
+            this.clearTimer();
+            this.callback('move', [evt]);
+        }
+        return true;
+    },
+
+    /**
+     * Method: passesTolerance
+     * Determine whether the mouse move is within the optional pixel tolerance.
+     *
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     *
+     * Returns:
+     * {Boolean} The mouse move is within the pixel tolerance.
+     */
+    passesTolerance: function(px) {
+        var passes = true;
+        if(this.pixelTolerance && this.px) {
+            var dpx = Math.sqrt(
+                Math.pow(this.px.x - px.x, 2) +
+                Math.pow(this.px.y - px.y, 2)
+            );
+            if(dpx < this.pixelTolerance) {
+                passes = false;
+            }
+        }
+        return passes;
+    },
+
+    /**
+     * Method: clearTimer
+     * Clear the timer and set <timerId> to null.
+     */
+    clearTimer: function() {
+        if(this.timerId != null) {
+            window.clearTimeout(this.timerId);
+            this.timerId = null;
+        }
+    },
+
+    /**
+     * Method: delayedCall
+     * Triggers pause callback.
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>}
+     */
+    delayedCall: function(evt) {
+        this.callback('pause', [evt]);
+    },
+
+    /**
+     * APIMethod: deactivate
+     * Deactivate the handler.
+     *
+     * Returns:
+     * {Boolean} The handler was successfully deactivated.
+     */
+    deactivate: function() {
+        var deactivated = false;
+        if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+            this.clearTimer();
+            deactivated = true;
+        }
+        return deactivated;
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Hover"
+});
+/* ======================================================================
+    OpenLayers/Handler/Keyboard.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.handler.Keyboard
+ * A handler for keyboard events.  Create a new instance with the
+ *     <OpenLayers.Handler.Keyboard> constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Handler> 
+ */
+OpenLayers.Handler.Keyboard = OpenLayers.Class(OpenLayers.Handler, {
+
+    /* http://www.quirksmode.org/js/keys.html explains key x-browser
+        key handling quirks in pretty nice detail */
+
+    /** 
+     * Constant: KEY_EVENTS
+     * keydown, keypress, keyup
+     */
+    KEY_EVENTS: ["keydown", "keyup"],
+
+    /** 
+    * Property: eventListener
+    * {Function}
+    */
+    eventListener: null,
+
+    /**
+     * Constructor: OpenLayers.Handler.Keyboard
+     * Returns a new keyboard handler.
+     * 
+     * Parameters:
+     * control - {<OpenLayers.Control>} The control that is making use of
+     *     this handler.  If a handler is being used without a control, the
+     *     handlers setMap method must be overridden to deal properly with
+     *     the map.
+     * callbacks - {Object} An object containing a single function to be
+     *     called when the drag operation is finished. The callback should
+     *     expect to recieve a single argument, the pixel location of the event.
+     *     Callbacks for 'keydown', 'keypress', and 'keyup' are supported.
+     * options - {Object} Optional object whose properties will be set on the
+     *     handler.
+     */
+    initialize: function(control, callbacks, options) {
+        OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+        // cache the bound event listener method so it can be unobserved later
+        this.eventListener = OpenLayers.Function.bindAsEventListener(
+            this.handleKeyEvent, this
+        );
+    },
+    
+    /**
+     * Method: destroy
+     */
+    destroy: function() {
+        this.deactivate();
+        this.eventListener = null;
+        OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * Method: activate
+     */
+    activate: function() {
+        if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+            for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+                OpenLayers.Event.observe(
+                    document, this.KEY_EVENTS[i], this.eventListener);
+            }
+            return true;
+        } else {
+            return false;
+        }
+    },
+
+    /**
+     * Method: deactivate
+     */
+    deactivate: function() {
+        var deactivated = false;
+        if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+            for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+                OpenLayers.Event.stopObserving(
+                    document, this.KEY_EVENTS[i], this.eventListener);
+            }
+            deactivated = true;
+        }
+        return deactivated;
+    },
+
+    /**
+     * Method: handleKeyEvent 
+     */
+    handleKeyEvent: function (evt) {
+        if (this.checkModifiers(evt)) {
+            this.callback(evt.type, [evt]);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Keyboard"
+});
+/* ======================================================================
     OpenLayers/Handler/MouseWheel.js
    ====================================================================== */
 
@@ -12016,7 +25039,7 @@
             }
 
             if (!overLayerDiv) {
-                for(var i=0; i < this.map.layers.length; i++) {
+                for(var i=0, len=this.map.layers.length; i<len; i++) {
                     // Are we in the layer div? Note that we have two cases
                     // here: one is to catch EventPane layers, which have a 
                     // pane above the layer (layer.pane)
@@ -12197,6 +25220,23 @@
     opacity: null,
 
     /**
+     * APIProperty: alwaysInRange
+     * {Boolean} If a layer's display should not be scale-based, this should 
+     *     be set to true. This will cause the layer, as an overlay, to always 
+     *     be 'active', by always returning true from the calculateInRange() 
+     *     function. 
+     * 
+     *     If not explicitly specified for a layer, its value will be 
+     *     determined on startup in initResolutions() based on whether or not 
+     *     any scale-specific properties have been set as options on the 
+     *     layer. If no scale-specific options have been set on the layer, we 
+     *     assume that it should always be in range.
+     * 
+     *     See #987 for more info.
+     */
+    alwaysInRange: null,   
+
+    /**
      * Constant: EVENT_TYPES
      * {Array(String)} Supported application event types.  Register a listener
      *     for a particular event with the following syntax:
@@ -12216,8 +25256,12 @@
      *  - *loadend* Triggered when layer loading ends.
      *  - *loadcancel* Triggered when layer loading is canceled.
      *  - *visibilitychanged* Triggered when layer visibility is changed.
+     *  - *moveend* Triggered when layer is moved, object passed as
+     *      argument has a zoomChanged boolean property which tells that the
+     *      zoom has changed.
      */
-    EVENT_TYPES: ["loadstart", "loadend", "loadcancel", "visibilitychanged"],
+    EVENT_TYPES: ["loadstart", "loadend", "loadcancel", "visibilitychanged",
+                  "moveend"],
         
     /**
      * APIProperty: events
@@ -12774,14 +25818,20 @@
      * 
      * Returns:
      * {Boolean} The layer is displayable at the current map's current
-     *     resolution.
+     *     resolution. Note that if 'alwaysInRange' is true for the layer, 
+     *     this function will always return true.
      */
     calculateInRange: function() {
         var inRange = false;
-        if (this.map) {
-            var resolution = this.map.getResolution();
-            inRange = ( (resolution >= this.minResolution) &&
-                        (resolution <= this.maxResolution) );
+
+        if (this.alwaysInRange) {
+            inRange = true;
+        } else {
+            if (this.map) {
+                var resolution = this.map.getResolution();
+                inRange = ( (resolution >= this.minResolution) &&
+                            (resolution <= this.maxResolution) );
+            }
         }
         return inRange;
     },
@@ -12836,27 +25886,60 @@
           'numZoomLevels', 'maxZoomLevel'
         );
 
+        //these are the properties which do *not* imply that user wishes 
+        // this layer to be scale-dependant
+        var notScaleProps = ['projection', 'units'];    
+
+        //should the layer be scale-dependant? default is false -- this will 
+        // only be set true if we find that the user has specified a property
+        // from the 'props' array that is not in 'notScaleProps'
+        var useInRange = false;
+
         // First we create a new object where we will store all of the 
         //  resolution-related properties that we find in either the layer's
         //  'options' array or from the map.
         //
         var confProps = {};        
-        for(var i=0; i < props.length; i++) {
+        for(var i=0, len=props.length; i<len; i++) {
             var property = props[i];
+            
+            // If the layer had one of these properties set *and* it is 
+            // a scale property (is not a non-scale property), then we assume
+            // the user did intend to use scale-dependant display (useInRange).
+            if (this.options[property] && 
+                OpenLayers.Util.indexOf(notScaleProps, property) == -1) {
+                useInRange = true;
+            }
+                   
             confProps[property] = this.options[property] || this.map[property];
         }
 
-        // Do not use the scales/resolutions at the map level if
-        // minScale/minResolution and maxScale/maxResolution are
-        // specified at the layer level
-        if (this.options.minScale != null &&
-            this.options.maxScale != null &&
+        //only automatically set 'alwaysInRange' if the user hasn't already 
+        // set it (to true or false, since the default is null). If user did
+        // not intend to use scale-dependant display then we set they layer
+        // as alwaysInRange. This means calculateInRange() will always return 
+        // true and the layer will never be turned off due to scale changes.
+        //
+        if (this.alwaysInRange == null) {
+            this.alwaysInRange = !useInRange;
+        }
+
+        // Do not use the scales array set at the map level if 
+        // either minScale or maxScale or both are set at the
+        // layer level
+        if ((this.options.minScale != null ||
+             this.options.maxScale != null) &&
             this.options.scales == null) {
+
             confProps.scales = null;
         }
-        if (this.options.minResolution != null &&
-            this.options.maxResolution != null &&
+        // Do not use the resolutions array set at the map level if 
+        // either minResolution or maxResolution or both are set at the
+        // layer level
+        if ((this.options.minResolution != null ||
+             this.options.maxResolution != null) &&
             this.options.resolutions == null) {
+
             confProps.resolutions = null;
         }
 
@@ -12874,7 +25957,7 @@
           //preset levels
             if (confProps.scales != null) {
                 confProps.resolutions = [];
-                for(var i = 0; i < confProps.scales.length; i++) {
+                for(var i=0, len=confProps.scales.length; i<len; i++) {
                     var scale = confProps.scales[i];
                     confProps.resolutions[i] = 
                        OpenLayers.Util.getResolutionFromScale(scale, 
@@ -12960,7 +26043,7 @@
         this.minResolution = confProps.resolutions[lastIndex];
         
         this.scales = [];
-        for(var i = 0; i < confProps.resolutions.length; i++) {
+        for(var i=0, len=confProps.resolutions.length; i<len; i++) {
             this.scales[i] = 
                OpenLayers.Util.getScaleFromResolution(confProps.resolutions[i], 
                                                       confProps.units);
@@ -13083,7 +26166,7 @@
             var highRes = this.resolutions[lowZoom];
             var lowRes = this.resolutions[highZoom];
             var res;
-            for(var i=0; i<this.resolutions.length; ++i) {
+            for(var i=0, len=this.resolutions.length; i<len; ++i) {
                 res = this.resolutions[i];
                 if(res >= resolution) {
                     highRes = res;
@@ -13104,7 +26187,7 @@
         } else {
             var diff;
             var minDiff = Number.POSITIVE_INFINITY;
-            for(var i=0; i < this.resolutions.length; i++) {            
+            for(var i=0, len=this.resolutions.length; i<len; i++) {            
                 if (closest) {
                     diff = Math.abs(this.resolutions[i] - resolution);
                     if (diff > minDiff) {
@@ -13189,7 +26272,7 @@
     setOpacity: function(opacity) {
         if (opacity != this.opacity) {
             this.opacity = opacity;
-            for(var i=0; i<this.div.childNodes.length; ++i) {
+            for(var i=0, len=this.div.childNodes.length; i<len; ++i) {
                 var element = this.div.childNodes[i].firstChild;
                 OpenLayers.Util.modifyDOMElement(element, null, null, null, 
                                                  null, null, null, opacity);
@@ -13198,6 +26281,16 @@
     },
 
     /**
+     * Method: getZIndex
+     * 
+     * Returns: 
+     * {Integer} the z-index of this layer
+     */    
+    getZIndex: function () {
+        return this.div.style.zIndex;
+    },
+
+    /**
      * Method: setZIndex
      * 
      * Parameters: 
@@ -13367,6 +26460,542 @@
 });
 
 /* ======================================================================
+    OpenLayers/Popup/FramedCloud.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Popup/Framed.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.FramedCloud
+ * 
+ * Inherits from: 
+ *  - <OpenLayers.Popup.Framed>
+ */
+OpenLayers.Popup.FramedCloud = 
+  OpenLayers.Class(OpenLayers.Popup.Framed, {
+
+    /** 
+     * Property: contentDisplayClass
+     * {String} The CSS class of the popup content div.
+     */
+    contentDisplayClass: "olFramedCloudPopupContent",
+
+    /**
+     * APIProperty: autoSize
+     * {Boolean} Framed Cloud is autosizing by default.
+     */
+    autoSize: true,
+
+    /**
+     * APIProperty: panMapIfOutOfView
+     * {Boolean} Framed Cloud does pan into view by default.
+     */
+    panMapIfOutOfView: true,
+
+    /**
+     * APIProperty: imageSize
+     * {<OpenLayers.Size>}
+     */
+    imageSize: new OpenLayers.Size(676, 736),
+
+    /**
+     * APIProperty: isAlphaImage
+     * {Boolean} The FramedCloud does not use an alpha image (in honor of the 
+     *     good ie6 folk out there)
+     */
+    isAlphaImage: false,
+
+    /** 
+     * APIProperty: fixedRelativePosition
+     * {Boolean} The Framed Cloud popup works in just one fixed position.
+     */
+    fixedRelativePosition: false,
+
+    /**
+     * Property: positionBlocks
+     * {Object} Hash of differen position blocks, keyed by relativePosition
+     *     two-character code string (ie "tl", "tr", "bl", "br")
+     */
+    positionBlocks: {
+        "tl": {
+            'offset': new OpenLayers.Pixel(44, 0),
+            'padding': new OpenLayers.Bounds(8, 40, 8, 9),
+            'blocks': [
+                { // top-left
+                    size: new OpenLayers.Size('auto', 'auto'),
+                    anchor: new OpenLayers.Bounds(0, 51, 22, 0),
+                    position: new OpenLayers.Pixel(0, 0)
+                },
+                { //top-right
+                    size: new OpenLayers.Size(22, 'auto'),
+                    anchor: new OpenLayers.Bounds(null, 50, 0, 0),
+                    position: new OpenLayers.Pixel(-638, 0)
+                },
+                { //bottom-left
+                    size: new OpenLayers.Size('auto', 21),
+                    anchor: new OpenLayers.Bounds(0, 32, 80, null),
+                    position: new OpenLayers.Pixel(0, -629)
+                },
+                { //bottom-right
+                    size: new OpenLayers.Size(22, 21),
+                    anchor: new OpenLayers.Bounds(null, 32, 0, null),
+                    position: new OpenLayers.Pixel(-638, -629)
+                },
+                { // stem
+                    size: new OpenLayers.Size(81, 54),
+                    anchor: new OpenLayers.Bounds(null, 0, 0, null),
+                    position: new OpenLayers.Pixel(0, -668)
+                }
+            ]
+        },
+        "tr": {
+            'offset': new OpenLayers.Pixel(-45, 0),
+            'padding': new OpenLayers.Bounds(8, 40, 8, 9),
+            'blocks': [
+                { // top-left
+                    size: new OpenLayers.Size('auto', 'auto'),
+                    anchor: new OpenLayers.Bounds(0, 51, 22, 0),
+                    position: new OpenLayers.Pixel(0, 0)
+                },
+                { //top-right
+                    size: new OpenLayers.Size(22, 'auto'),
+                    anchor: new OpenLayers.Bounds(null, 50, 0, 0),
+                    position: new OpenLayers.Pixel(-638, 0)
+                },
+                { //bottom-left
+                    size: new OpenLayers.Size('auto', 21),
+                    anchor: new OpenLayers.Bounds(0, 32, 22, null),
+                    position: new OpenLayers.Pixel(0, -629)
+                },
+                { //bottom-right
+                    size: new OpenLayers.Size(22, 21),
+                    anchor: new OpenLayers.Bounds(null, 32, 0, null),
+                    position: new OpenLayers.Pixel(-638, -629)
+                },
+                { // stem
+                    size: new OpenLayers.Size(81, 54),
+                    anchor: new OpenLayers.Bounds(0, 0, null, null),
+                    position: new OpenLayers.Pixel(-215, -668)
+                }
+            ]
+        },
+        "bl": {
+            'offset': new OpenLayers.Pixel(45, 0),
+            'padding': new OpenLayers.Bounds(8, 9, 8, 40),
+            'blocks': [
+                { // top-left
+                    size: new OpenLayers.Size('auto', 'auto'),
+                    anchor: new OpenLayers.Bounds(0, 21, 22, 32),
+                    position: new OpenLayers.Pixel(0, 0)
+                },
+                { //top-right
+                    size: new OpenLayers.Size(22, 'auto'),
+                    anchor: new OpenLayers.Bounds(null, 21, 0, 32),
+                    position: new OpenLayers.Pixel(-638, 0)
+                },
+                { //bottom-left
+                    size: new OpenLayers.Size('auto', 21),
+                    anchor: new OpenLayers.Bounds(0, 0, 22, null),
+                    position: new OpenLayers.Pixel(0, -629)
+                },
+                { //bottom-right
+                    size: new OpenLayers.Size(22, 21),
+                    anchor: new OpenLayers.Bounds(null, 0, 0, null),
+                    position: new OpenLayers.Pixel(-638, -629)
+                },
+                { // stem
+                    size: new OpenLayers.Size(81, 54),
+                    anchor: new OpenLayers.Bounds(null, null, 0, 0),
+                    position: new OpenLayers.Pixel(-101, -674)
+                }
+            ]
+        },
+        "br": {
+            'offset': new OpenLayers.Pixel(-44, 0),
+            'padding': new OpenLayers.Bounds(8, 9, 8, 40),
+            'blocks': [
+                { // top-left
+                    size: new OpenLayers.Size('auto', 'auto'),
+                    anchor: new OpenLayers.Bounds(0, 21, 22, 32),
+                    position: new OpenLayers.Pixel(0, 0)
+                },
+                { //top-right
+                    size: new OpenLayers.Size(22, 'auto'),
+                    anchor: new OpenLayers.Bounds(null, 21, 0, 32),
+                    position: new OpenLayers.Pixel(-638, 0)
+                },
+                { //bottom-left
+                    size: new OpenLayers.Size('auto', 21),
+                    anchor: new OpenLayers.Bounds(0, 0, 22, null),
+                    position: new OpenLayers.Pixel(0, -629)
+                },
+                { //bottom-right
+                    size: new OpenLayers.Size(22, 21),
+                    anchor: new OpenLayers.Bounds(null, 0, 0, null),
+                    position: new OpenLayers.Pixel(-638, -629)
+                },
+                { // stem
+                    size: new OpenLayers.Size(81, 54),
+                    anchor: new OpenLayers.Bounds(0, null, null, 0),
+                    position: new OpenLayers.Pixel(-311, -674)
+                }
+            ]
+        }
+    },
+
+    /**
+     * APIProperty: minSize
+     * {<OpenLayers.Size>}
+     */
+    minSize: new OpenLayers.Size(105, 10),
+
+    /**
+     * APIProperty: maxSize
+     * {<OpenLayers.Size>}
+     */
+    maxSize: new OpenLayers.Size(600, 660),
+
+    /** 
+     * Constructor: OpenLayers.Popup.FramedCloud
+     * 
+     * Parameters:
+     * id - {String}
+     * lonlat - {<OpenLayers.LonLat>}
+     * contentSize - {<OpenLayers.Size>}
+     * contentHTML - {String}
+     * anchor - {Object} Object to which we'll anchor the popup. Must expose 
+     *     a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>) 
+     *     (Note that this is generally an <OpenLayers.Icon>).
+     * closeBox - {Boolean}
+     * closeBoxCallback - {Function} Function to be called on closeBox click.
+     */
+    initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox, 
+                        closeBoxCallback) {
+
+        this.imageSrc = OpenLayers.Util.getImagesLocation() + 'cloud-popup-relative.png';
+        OpenLayers.Popup.Framed.prototype.initialize.apply(this, arguments);
+        this.contentDiv.className = this.contentDisplayClass;
+    },
+
+    /** 
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        OpenLayers.Popup.Framed.prototype.destroy.apply(this, arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Popup.FramedCloud"
+});
+/* ======================================================================
+    OpenLayers/Control/DragFeature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ * @requires OpenLayers/Handler/Feature.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DragFeature
+ * Move a feature with a drag.  Create a new control with the
+ *     <OpenLayers.Control.DragFeature> constructor.
+ *
+ * Inherits From:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.DragFeature = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * APIProperty: geometryTypes
+     * {Array(String)} To restrict dragging to a limited set of geometry types,
+     *     send a list of strings corresponding to the geometry class names.
+     */
+    geometryTypes: null,
+    
+    /**
+     * APIProperty: onStart
+     * {Function} Define this function if you want to know when a drag starts.
+     *     The function should expect to receive two arguments: the feature
+     *     that is about to be dragged and the pixel location of the mouse.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature that is about to be
+     *     dragged.
+     * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+     */
+    onStart: function(feature, pixel) {},
+
+    /**
+     * APIProperty: onDrag
+     * {Function} Define this function if you want to know about each move of a
+     *     feature. The function should expect to receive two arguments: the
+     *     feature that is being dragged and the pixel location of the mouse.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+     * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+     */
+    onDrag: function(feature, pixel) {},
+
+    /**
+     * APIProperty: onComplete
+     * {Function} Define this function if you want to know when a feature is
+     *     done dragging. The function should expect to receive two arguments:
+     *     the feature that is being dragged and the pixel location of the
+     *     mouse.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+     * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+     */
+    onComplete: function(feature, pixel) {},
+
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>}
+     */
+    layer: null,
+    
+    /**
+     * Property: feature
+     * {<OpenLayers.Feature.Vector>}
+     */
+    feature: null,
+
+    /**
+     * Property: dragCallbacks
+     * {Object} The functions that are sent to the drag handler for callback.
+     */
+    dragCallbacks: {},
+
+    /**
+     * Property: featureCallbacks
+     * {Object} The functions that are sent to the feature handler for callback.
+     */
+    featureCallbacks: {},
+    
+    /**
+     * Property: lastPixel
+     * {<OpenLayers.Pixel>}
+     */
+    lastPixel: null,
+
+    /**
+     * Constructor: OpenLayers.Control.DragFeature
+     * Create a new control to drag features.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.Vector>} The layer containing features to be
+     *     dragged.
+     * options - {Object} Optional object whose properties will be set on the
+     *     control.
+     */
+    initialize: function(layer, options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.layer = layer;
+        this.handlers = {
+            drag: new OpenLayers.Handler.Drag(
+                this, OpenLayers.Util.extend({
+                    down: this.downFeature,
+                    move: this.moveFeature,
+                    up: this.upFeature,
+                    out: this.cancel,
+                    done: this.doneDragging
+                }, this.dragCallbacks)
+            ),
+            feature: new OpenLayers.Handler.Feature(
+                this, this.layer, OpenLayers.Util.extend({
+                    over: this.overFeature,
+                    out: this.outFeature
+                }, this.featureCallbacks),
+                {geometryTypes: this.geometryTypes}
+            )
+        };
+    },
+    
+    /**
+     * APIMethod: destroy
+     * Take care of things that are not handled in superclass
+     */
+    destroy: function() {
+        this.layer = null;
+        OpenLayers.Control.prototype.destroy.apply(this, []);
+    },
+
+    /**
+     * APIMethod: activate
+     * Activate the control and the feature handler.
+     * 
+     * Returns:
+     * {Boolean} Successfully activated the control and feature handler.
+     */
+    activate: function() {
+        return (this.handlers.feature.activate() &&
+                OpenLayers.Control.prototype.activate.apply(this, arguments));
+    },
+
+    /**
+     * APIMethod: deactivate
+     * Deactivate the control and all handlers.
+     * 
+     * Returns:
+     * {Boolean} Successfully deactivated the control.
+     */
+    deactivate: function() {
+        // the return from the handlers is unimportant in this case
+        this.handlers.drag.deactivate();
+        this.handlers.feature.deactivate();
+        this.feature = null;
+        this.dragging = false;
+        this.lastPixel = null;
+        return OpenLayers.Control.prototype.deactivate.apply(this, arguments);
+    },
+
+    /**
+     * Method: overFeature
+     * Called when the feature handler detects a mouse-over on a feature.
+     *     This activates the drag handler.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The selected feature.
+     */
+    overFeature: function(feature) {
+        if(!this.handlers.drag.dragging) {
+            this.feature = feature;
+            this.handlers.drag.activate();
+            this.over = true;
+            // TBD replace with CSS classes
+            this.map.div.style.cursor = "move";
+        } else {
+            if(this.feature.id == feature.id) {
+                this.over = true;
+            } else {
+                this.over = false;
+            }
+        }
+    },
+
+    /**
+     * Method: downFeature
+     * Called when the drag handler detects a mouse-down.
+     *
+     * Parameters:
+     * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+     */
+    downFeature: function(pixel) {
+        this.lastPixel = pixel;
+        this.onStart(this.feature, pixel);
+    },
+
+    /**
+     * Method: moveFeature
+     * Called when the drag handler detects a mouse-move.  Also calls the
+     *     optional onDrag method.
+     * 
+     * Parameters:
+     * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+     */
+    moveFeature: function(pixel) {
+        var res = this.map.getResolution();
+        this.feature.geometry.move(res * (pixel.x - this.lastPixel.x),
+                                   res * (this.lastPixel.y - pixel.y));
+        this.layer.drawFeature(this.feature);
+        this.lastPixel = pixel;
+        this.onDrag(this.feature, pixel);
+    },
+
+    /**
+     * Method: upFeature
+     * Called when the drag handler detects a mouse-up.  Also calls the
+     *     optional onComplete method.
+     * 
+     * Parameters:
+     * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+     */
+    upFeature: function(pixel) {
+        if(!this.over) {
+            this.handlers.drag.deactivate();
+            this.feature = null;
+            // TBD replace with CSS classes
+            this.map.div.style.cursor = "default";
+        } else {
+            // the drag handler itself resetted the cursor, so
+            // set it back to "move" here
+            this.map.div.style.cursor = "move";
+        }
+    },
+
+    /**
+     * Method: doneDragging
+     * Called when the drag handler is done dragging.
+     *
+     * Parameters:
+     * pixel - {<OpenLayers.Pixel>} The last event pixel location.  If this event
+     *     came from a mouseout, this may not be in the map viewport.
+     */
+    doneDragging: function(pixel) {
+        this.onComplete(this.feature, pixel);
+    },
+
+    /**
+     * Method: outFeature
+     * Called when the feature handler detects a mouse-out on a feature.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature that the mouse left.
+     */
+    outFeature: function(feature) {
+        if(!this.handlers.drag.dragging) {
+            this.over = false;
+            this.handlers.drag.deactivate();
+            // TBD replace with CSS classes
+            this.map.div.style.cursor = "default";
+            this.feature = null;
+        } else {
+            if(this.feature.id == feature.id) {
+                this.over = false;
+            }
+        }
+    },
+        
+    /**
+     * Method: cancel
+     * Called when the drag handler detects a mouse-out (from the map viewport).
+     */
+    cancel: function() {
+        this.handlers.drag.deactivate();
+        this.over = false;
+    },
+
+    /**
+     * Method: setMap
+     * Set the map property for the control and all handlers.
+     *
+     * Parameters: 
+     * map - {<OpenLayers.Map>} The control's map.
+     */
+    setMap: function(map) {
+        this.handlers.drag.setMap(map);
+        this.handlers.feature.setMap(map);
+        OpenLayers.Control.prototype.setMap.apply(this, arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Control.DragFeature"
+});
+/* ======================================================================
     OpenLayers/Control/DragPan.js
    ====================================================================== */
 
@@ -13401,13 +27030,26 @@
     panned: false,
     
     /**
+     * Property: interval
+     * {Integer} The number of milliseconds that should ellapse before
+     *     panning the map again. Set this to increase dragging performance.
+     *     Defaults to 25 milliseconds.
+     */
+    interval: 25, 
+    
+    /**
      * Method: draw
      * Creates a Drag handler, using <panMap> and
      * <panMapDone> as callbacks.
      */    
     draw: function() {
-        this.handler = new OpenLayers.Handler.Drag(this,
-                            {"move": this.panMap, "done": this.panMapDone});
+        this.handler = new OpenLayers.Handler.Drag(this, {
+                "move": this.panMap,
+                "done": this.panMapDone
+            }, {
+                interval: this.interval
+            }
+        );
     },
 
     /**
@@ -13443,6 +27085,777 @@
     CLASS_NAME: "OpenLayers.Control.DragPan"
 });
 /* ======================================================================
+    OpenLayers/Control/KeyboardDefaults.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Keyboard.js
+ */
+
+/**
+ * Class: OpenLayers.Control.KeyboardDefaults
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.KeyboardDefaults = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * APIProperty: slideFactor
+     * Pixels to slide by.
+     */
+    slideFactor: 75,
+
+    /**
+     * Constructor: OpenLayers.Control.KeyboardDefaults
+     */
+    initialize: function() {
+        OpenLayers.Control.prototype.initialize.apply(this, arguments);
+    },
+    
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        if (this.handler) {
+            this.handler.destroy();
+        }        
+        this.handler = null;
+        
+        OpenLayers.Control.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * Method: draw
+     * Create handler.
+     */
+    draw: function() {
+        this.handler = new OpenLayers.Handler.Keyboard( this, { 
+                                "keydown": this.defaultKeyPress });
+        this.activate();
+    },
+    
+    /**
+     * Method: defaultKeyPress
+     * When handling the key event, we only use evt.keyCode. This holds 
+     * some drawbacks, though we get around them below. When interpretting
+     * the keycodes below (including the comments associated with them),
+     * consult the URL below. For instance, the Safari browser returns
+     * "IE keycodes", and so is supported by any keycode labeled "IE".
+     * 
+     * Very informative URL:
+     *    http://unixpapa.com/js/key.html
+     *
+     * Parameters:
+     * code - {Integer} 
+     */
+    defaultKeyPress: function (evt) {
+        switch(evt.keyCode) {
+            case OpenLayers.Event.KEY_LEFT:
+                this.map.pan(-this.slideFactor, 0);
+                break;
+            case OpenLayers.Event.KEY_RIGHT: 
+                this.map.pan(this.slideFactor, 0);
+                break;
+            case OpenLayers.Event.KEY_UP:
+                this.map.pan(0, -this.slideFactor);
+                break;
+            case OpenLayers.Event.KEY_DOWN:
+                this.map.pan(0, this.slideFactor);
+                break;
+            
+            case 33: // Page Up. Same in all browsers.
+                var size = this.map.getSize();
+                this.map.pan(0, -0.75*size.h);
+                break;
+            case 34: // Page Down. Same in all browsers.
+                var size = this.map.getSize();
+                this.map.pan(0, 0.75*size.h);
+                break; 
+            case 35: // End. Same in all browsers.
+                var size = this.map.getSize();
+                this.map.pan(0.75*size.w, 0);
+                break; 
+            case 36: // Home. Same in all browsers.
+                var size = this.map.getSize();
+                this.map.pan(-0.75*size.w, 0);
+                break; 
+
+            case 43:  // +/= (ASCII), keypad + (ASCII, Opera)
+            case 61:  // +/= (Mozilla, Opera, some ASCII)
+            case 187: // +/= (IE)
+            case 107: // keypad + (IE, Mozilla)
+                this.map.zoomIn();
+                break; 
+            case 45:  // -/_ (ASCII, Opera), keypad - (ASCII, Opera)
+            case 109: // -/_ (Mozilla), keypad - (Mozilla, IE)
+            case 189: // -/_ (IE)
+            case 95:  // -/_ (some ASCII)
+                this.map.zoomOut();
+                break; 
+        } 
+    },
+
+    CLASS_NAME: "OpenLayers.Control.KeyboardDefaults"
+});
+/* ======================================================================
+    OpenLayers/Feature/Vector.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+// TRASH THIS
+OpenLayers.State = {
+    /** states */
+    UNKNOWN: 'Unknown',
+    INSERT: 'Insert',
+    UPDATE: 'Update',
+    DELETE: 'Delete'
+};
+
+/**
+ * @requires OpenLayers/Feature.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature.Vector
+ * Vector features use the OpenLayers.Geometry classes as geometry description.
+ * They have an 'attributes' property, which is the data object, and a 'style'
+ * property, the default values of which are defined in the 
+ * <OpenLayers.Feature.Vector.style> objects.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Feature>
+ */
+OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, {
+
+    /** 
+     * Property: fid 
+     * {String} 
+     */
+    fid: null,
+    
+    /** 
+     * APIProperty: geometry 
+     * {<OpenLayers.Geometry>} 
+     */
+    geometry: null,
+
+    /** 
+     * APIProperty: attributes 
+     * {Object} This object holds arbitrary properties that describe the
+     *     feature.
+     */
+    attributes: null,
+
+    /** 
+     * Property: state 
+     * {String} 
+     */
+    state: null,
+    
+    /** 
+     * APIProperty: style 
+     * {Object} 
+     */
+    style: null,
+    
+    /**
+     * Property: renderIntent
+     * {String} rendering intent currently being used
+     */
+    renderIntent: "default",
+
+    /** 
+     * Constructor: OpenLayers.Feature.Vector
+     * Create a vector feature. 
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} The geometry that this feature
+     *     represents.
+     * attributes - {Object} An optional object that will be mapped to the
+     *     <attributes> property. 
+     * style - {Object} An optional style object.
+     */
+    initialize: function(geometry, attributes, style) {
+        OpenLayers.Feature.prototype.initialize.apply(this,
+                                                      [null, null, attributes]);
+        this.lonlat = null;
+        this.geometry = geometry ? geometry : null;
+        this.state = null;
+        this.attributes = {};
+        if (attributes) {
+            this.attributes = OpenLayers.Util.extend(this.attributes,
+                                                     attributes);
+        }
+        this.style = style ? style : null; 
+    },
+    
+    /** 
+     * Method: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+        if (this.layer) {
+            this.layer.removeFeatures(this);
+            this.layer = null;
+        }
+            
+        this.geometry = null;
+        OpenLayers.Feature.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * Method: clone
+     * Create a clone of this vector feature.  Does not set any non-standard
+     *     properties.
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} An exact clone of this vector feature.
+     */
+    clone: function () {
+        return new OpenLayers.Feature.Vector(
+            this.geometry ? this.geometry.clone() : null,
+            this.attributes,
+            this.style);
+    },
+
+    /**
+     * Method: onScreen
+     * Determine whether the feature is within the map viewport.  This method
+     *     tests for an intersection between the geometry and the viewport
+     *     bounds.  If a more effecient but less precise geometry bounds
+     *     intersection is desired, call the method with the boundsOnly
+     *     parameter true.
+     *
+     * Parameters:
+     * boundsOnly - {Boolean} Only test whether a feature's bounds intersects
+     *     the viewport bounds.  Default is false.  If false, the feature's
+     *     geometry must intersect the viewport for onScreen to return true.
+     * 
+     * Returns:
+     * {Boolean} The feature is currently visible on screen (optionally
+     *     based on its bounds if boundsOnly is true).
+     */
+    onScreen:function(boundsOnly) {
+        var onScreen = false;
+        if(this.layer && this.layer.map) {
+            var screenBounds = this.layer.map.getExtent();
+            if(boundsOnly) {
+                var featureBounds = this.geometry.getBounds();
+                onScreen = screenBounds.intersectsBounds(featureBounds);
+            } else {
+                var screenPoly = screenBounds.toGeometry();
+                onScreen = screenPoly.intersects(this.geometry);
+            }
+        }    
+        return onScreen;
+    },
+    
+    /**
+     * Method: createMarker
+     * HACK - we need to decide if all vector features should be able to
+     *     create markers
+     * 
+     * Returns:
+     * {<OpenLayers.Marker>} For now just returns null
+     */
+    createMarker: function() {
+        return null;
+    },
+
+    /**
+     * Method: destroyMarker
+     * HACK - we need to decide if all vector features should be able to
+     *     delete markers
+     * 
+     * If user overrides the createMarker() function, s/he should be able
+     *   to also specify an alternative function for destroying it
+     */
+    destroyMarker: function() {
+        // pass
+    },
+
+    /**
+     * Method: createPopup
+     * HACK - we need to decide if all vector features should be able to
+     *     create popups
+     * 
+     * Returns:
+     * {<OpenLayers.Popup>} For now just returns null
+     */
+    createPopup: function() {
+        return null;
+    },
+
+    /**
+     * Method: atPoint
+     * Determins whether the feature intersects with the specified location.
+     * 
+     * Parameters: 
+     * lonlat - {<OpenLayers.LonLat>} 
+     * toleranceLon - {float} Optional tolerance in Geometric Coords
+     * toleranceLat - {float} Optional tolerance in Geographic Coords
+     * 
+     * Returns:
+     * {Boolean} Whether or not the feature is at the specified location
+     */
+    atPoint: function(lonlat, toleranceLon, toleranceLat) {
+        var atPoint = false;
+        if(this.geometry) {
+            atPoint = this.geometry.atPoint(lonlat, toleranceLon, 
+                                                    toleranceLat);
+        }
+        return atPoint;
+    },
+
+    /**
+     * Method: destroyPopup
+     * HACK - we need to decide if all vector features should be able to
+     * delete popups
+     */
+    destroyPopup: function() {
+        // pass
+    },
+
+    /**
+     * Method: move
+     * Moves the feature and redraws it at its new location
+     *
+     * Parameters:
+     * state - {OpenLayers.LonLat or OpenLayers.Pixel} the
+     *         location to which to move the feature.
+     */
+    move: function(location) {
+
+        if(!this.layer || !this.geometry.move){
+            //do nothing if no layer or immoveable geometry
+            return;
+        }
+
+        var pixel;
+        if (location.CLASS_NAME == "OpenLayers.LonLat") {
+            pixel = this.layer.getViewPortPxFromLonLat(location);
+        } else {
+            pixel = location;
+        }
+        
+        var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());
+        var res = this.layer.map.getResolution();
+        this.geometry.move(res * (pixel.x - lastPixel.x),
+                           res * (lastPixel.y - pixel.y));
+        this.layer.drawFeature(this);
+        return lastPixel;
+    },
+    
+    /**
+     * Method: toState
+     * Sets the new state
+     *
+     * Parameters:
+     * state - {String} 
+     */
+    toState: function(state) {
+        if (state == OpenLayers.State.UPDATE) {
+            switch (this.state) {
+                case OpenLayers.State.UNKNOWN:
+                case OpenLayers.State.DELETE:
+                    this.state = state;
+                    break;
+                case OpenLayers.State.UPDATE:
+                case OpenLayers.State.INSERT:
+                    break;
+            }
+        } else if (state == OpenLayers.State.INSERT) {
+            switch (this.state) {
+                case OpenLayers.State.UNKNOWN:
+                    break;
+                default:
+                    this.state = state;
+                    break;
+            }
+        } else if (state == OpenLayers.State.DELETE) {
+            switch (this.state) {
+                case OpenLayers.State.INSERT:
+                    // the feature should be destroyed
+                    break;
+                case OpenLayers.State.DELETE:
+                    break;
+                case OpenLayers.State.UNKNOWN:
+                case OpenLayers.State.UPDATE:
+                    this.state = state;
+                    break;
+            }
+        } else if (state == OpenLayers.State.UNKNOWN) {
+            this.state = state;
+        }
+    },
+    
+    CLASS_NAME: "OpenLayers.Feature.Vector"
+});
+
+
+/**
+ * Constant: OpenLayers.Feature.Vector.style
+ * OpenLayers features can have a number of style attributes. The 'default' 
+ *     style will typically be used if no other style is specified.
+ *
+ * Default style properties:
+ *
+ *  - fillColor: "#ee9900",
+ *  - fillOpacity: 0.4, 
+ *  - hoverFillColor: "white",
+ *  - hoverFillOpacity: 0.8,
+ *  - strokeColor: "#ee9900",
+ *  - strokeOpacity: 1,
+ *  - strokeWidth: 1,
+ *  - strokeLinecap: "round",  [butt | round | square]
+ *  - strokeDashstyle: "solid", [dot | dash | dashdot | longdash | longdashdot | solid]
+ *  - hoverStrokeColor: "red",
+ *  - hoverStrokeOpacity: 1,
+ *  - hoverStrokeWidth: 0.2,
+ *  - pointRadius: 6,
+ *  - hoverPointRadius: 1,
+ *  - hoverPointUnit: "%",
+ *  - pointerEvents: "visiblePainted"
+ *  - cursor: ""
+ *
+ * Other style properties that have no default values:
+ *
+ *  - externalGraphic,
+ *  - graphicWidth,
+ *  - graphicHeight,
+ *  - graphicOpacity,
+ *  - graphicXOffset,
+ *  - graphicYOffset,
+ *  - graphicName,
+ *  - display
+ */ 
+OpenLayers.Feature.Vector.style = {
+    'default': {
+        fillColor: "#ee9900",
+        fillOpacity: 0.4, 
+        hoverFillColor: "white",
+        hoverFillOpacity: 0.8,
+        strokeColor: "#ee9900",
+        strokeOpacity: 1,
+        strokeWidth: 1,
+        strokeLinecap: "round",
+        strokeDashstyle: "solid",
+        hoverStrokeColor: "red",
+        hoverStrokeOpacity: 1,
+        hoverStrokeWidth: 0.2,
+        pointRadius: 6,
+        hoverPointRadius: 1,
+        hoverPointUnit: "%",
+        pointerEvents: "visiblePainted",
+        cursor: "inherit"
+    },
+    'select': {
+        fillColor: "blue",
+        fillOpacity: 0.4, 
+        hoverFillColor: "white",
+        hoverFillOpacity: 0.8,
+        strokeColor: "blue",
+        strokeOpacity: 1,
+        strokeWidth: 2,
+        strokeLinecap: "round",
+        strokeDashstyle: "solid",
+        hoverStrokeColor: "red",
+        hoverStrokeOpacity: 1,
+        hoverStrokeWidth: 0.2,
+        pointRadius: 6,
+        hoverPointRadius: 1,
+        hoverPointUnit: "%",
+        pointerEvents: "visiblePainted",
+        cursor: "pointer"
+    },
+    'temporary': {
+        fillColor: "yellow",
+        fillOpacity: 0.2, 
+        hoverFillColor: "white",
+        hoverFillOpacity: 0.8,
+        strokeColor: "yellow",
+        strokeOpacity: 1,
+        strokeLinecap: "round",
+        strokeWidth: 4,
+        strokeDashstyle: "solid",
+        hoverStrokeColor: "red",
+        hoverStrokeOpacity: 1,
+        hoverStrokeWidth: 0.2,
+        pointRadius: 6,
+        hoverPointRadius: 1,
+        hoverPointUnit: "%",
+        pointerEvents: "visiblePainted",
+        cursor: "inherit"
+    }
+};    
+/* ======================================================================
+    OpenLayers/Feature/WFS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Feature.js
+ */
+
+/**
+ * Class: OpenLayers.Feature.WFS
+ * WFS handling class, for use as a featureClass on the WFS layer for handling
+ * 'point' WFS types. Good for subclassing when creating a custom WFS like
+ * XML application.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Feature>
+ */
+OpenLayers.Feature.WFS = OpenLayers.Class(OpenLayers.Feature, {
+      
+    /** 
+     * Constructor: OpenLayers.Feature.WFS
+     * Create a WFS feature.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer>} 
+     * xmlNode - {XMLNode} 
+     */
+    initialize: function(layer, xmlNode) {
+        var newArguments = arguments;
+        var data = this.processXMLNode(xmlNode);
+        newArguments = new Array(layer, data.lonlat, data);
+        OpenLayers.Feature.prototype.initialize.apply(this, newArguments);
+        this.createMarker();
+        this.layer.addMarker(this.marker);
+    },
+    
+    /** 
+     * Method: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+        if (this.marker != null) {
+            this.layer.removeMarker(this.marker);  
+        }
+        OpenLayers.Feature.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * Method: processXMLNode
+     * When passed an xmlNode, parses it for a GML point, and passes
+     * back an object describing that point.
+     *
+     * For subclasses of Feature.WFS, this is the feature to change.
+     *
+     * Parameters:
+     * xmlNode - {XMLNode} 
+     * 
+     * Returns:
+     * {Object} Data Object with 'id', 'lonlat', and private properties set
+     */
+    processXMLNode: function(xmlNode) {
+        //this should be overridden by subclasses
+        // must return an Object with 'id' and 'lonlat' values set
+        var point = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, "http://www.opengis.net/gml", "gml", "Point");
+        var text  = OpenLayers.Util.getXmlNodeValue(OpenLayers.Ajax.getElementsByTagNameNS(point[0], "http://www.opengis.net/gml","gml", "coordinates")[0]);
+        var floats = text.split(",");
+        return {lonlat: new OpenLayers.LonLat(parseFloat(floats[0]),
+                                              parseFloat(floats[1])),
+                id: null};
+
+    },
+
+    CLASS_NAME: "OpenLayers.Feature.WFS"
+});
+  
+  
+  
+  
+
+/* ======================================================================
+    OpenLayers/Format/WMC/v1_0_0.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMC/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1_0_0
+ * Read and write WMC version 1.0.0.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.WMC.v1>
+ */
+OpenLayers.Format.WMC.v1_0_0 = OpenLayers.Class(
+    OpenLayers.Format.WMC.v1, {
+    
+    /**
+     * Constant: VERSION
+     * {String} 1.0.0
+     */
+    VERSION: "1.0.0",
+    
+    /**
+     * Property: schemaLocation
+     * {String} http://www.opengis.net/context
+     *     http://schemas.opengis.net/context/1.0.0/context.xsd
+     */
+    schemaLocation: "http://www.opengis.net/context http://schemas.opengis.net/context/1.0.0/context.xsd",
+
+    /**
+     * Constructor: OpenLayers.Format.WMC.v1_0_0
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.WMC> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.WMC.v1.prototype.initialize.apply(
+            this, [options]
+        );
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WMC.v1_0_0" 
+
+});
+/* ======================================================================
+    OpenLayers/Format/WMC/v1_1_0.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMC/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1_1_0
+ * Read and write WMC version 1.1.0.
+ *
+ * Differences between 1.1.0 and 1.0.0:
+ *     - 1.1.0 Layers have optional sld:MinScaleDenominator and
+ *       sld:MaxScaleDenominator
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.WMC.v1>
+ */
+OpenLayers.Format.WMC.v1_1_0 = OpenLayers.Class(
+    OpenLayers.Format.WMC.v1, {
+    
+    /**
+     * Constant: VERSION
+     * {String} 1.1.0
+     */
+    VERSION: "1.1.0",
+
+    /**
+     * Property: schemaLocation
+     * {String} http://www.opengis.net/context
+     *     http://schemas.opengis.net/context/1.1.0/context.xsd
+     */
+    schemaLocation: "http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd",
+
+    /**
+     * Constructor: OpenLayers.Format.WMC.v1_1_0
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.WMC> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.WMC.v1.prototype.initialize.apply(
+            this, [options]
+        );
+    },
+
+    /**
+     * Method: read_sld_MinScaleDenominator
+     * Read a sld:MinScaleDenominator node.
+     *
+     * Parameters:
+     * layerInfo - {Object} An object representing a layer.
+     * node - {Element} An element node.
+     */
+    read_sld_MinScaleDenominator: function(layerInfo, node) {
+        layerInfo.options.maxScale = this.getChildValue(node);
+    },
+
+    /**
+     * Method: read_sld_MaxScaleDenominator
+     * Read a sld:MaxScaleDenominator node.
+     *
+     * Parameters:
+     * layerInfo - {Object} An object representing a layer.
+     * node - {Element} An element node.
+     */
+    read_sld_MaxScaleDenominator: function(layerInfo, node) {
+        layerInfo.options.minScale = this.getChildValue(node);
+    },
+
+    /**
+     * Method: write_wmc_Layer
+     * Create a Layer node given a layer object.  This method adds elements
+     *     specific to version 1.1.0.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC Layer element node.
+     */
+    write_wmc_Layer: function(layer) {
+        var node = OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(
+            this, [layer]
+        );
+        
+        // min/max scale denominator elements go before the 4th element in v1
+        if(layer.options.resolutions || layer.options.scales ||
+           layer.options.minResolution || layer.options.maxScale) {
+            var minSD = this.createElementNS(
+                this.namespaces.sld, "sld:MinScaleDenominator"
+            );
+            minSD.appendChild(this.createTextNode(layer.maxScale.toPrecision(10)));
+            node.insertBefore(minSD, node.childNodes[3]);
+        }
+        
+        if(layer.options.resolutions || layer.options.scales ||
+           layer.options.maxResolution || layer.options.minScale) {
+            var maxSD = this.createElementNS(
+                this.namespaces.sld, "sld:MaxScaleDenominator"
+            );
+            maxSD.appendChild(this.createTextNode(layer.minScale.toPrecision(10)));
+            node.insertBefore(maxSD, node.childNodes[4]);
+        }
+        
+        return node;
+        
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WMC.v1_1_0" 
+
+});
+/* ======================================================================
     OpenLayers/Handler/Box.js
    ====================================================================== */
 
@@ -13477,6 +27890,13 @@
      *     olHandlerBoxZoomBox
      */
     boxDivClassName: 'olHandlerBoxZoomBox',
+    
+    /**
+     * Property: boxCharacteristics
+     * {Object} Caches some box characteristics from css. This is used
+     *     by the getBoxCharacteristics method.
+     */
+    boxCharacteristics: null,
 
     /**
      * Constructor: OpenLayers.Handler.Box
@@ -13532,16 +27952,28 @@
     * Method: moveBox
     */
     moveBox: function (xy) {
-        var deltaX = Math.abs(this.dragHandler.start.x - xy.x);
-        var deltaY = Math.abs(this.dragHandler.start.y - xy.y);
+        var startX = this.dragHandler.start.x;
+        var startY = this.dragHandler.start.y;
+        var deltaX = Math.abs(startX - xy.x);
+        var deltaY = Math.abs(startY - xy.y);
         this.zoomBox.style.width = Math.max(1, deltaX) + "px";
         this.zoomBox.style.height = Math.max(1, deltaY) + "px";
-        if (xy.x < this.dragHandler.start.x) {
-            this.zoomBox.style.left = xy.x+"px";
+        this.zoomBox.style.left = xy.x < startX ? xy.x+"px" : startX+"px";
+        this.zoomBox.style.top = xy.y < startY ? xy.y+"px" : startY+"px";
+
+        // depending on the box model, modify width and height to take borders
+        // of the box into account
+        var box = this.getBoxCharacteristics(deltaX, deltaY);
+        if (box.newBoxModel) {
+            if (xy.x > startX) {
+                this.zoomBox.style.width =
+                    Math.max(1, deltaX - box.xOffset) + "px";
+            }
+            if (xy.y > startY) {
+                this.zoomBox.style.height =
+                    Math.max(1, deltaY - box.yOffset) + "px";
+            }
         }
-        if (xy.y < this.dragHandler.start.y) {
-            this.zoomBox.style.top = xy.y+"px";
-        }
     },
 
     /**
@@ -13575,6 +28007,7 @@
     removeBox: function() {
         this.map.viewPortDiv.removeChild(this.zoomBox);
         this.zoomBox = null;
+        this.boxCharacteristics = null;
     },
 
     /**
@@ -13600,10 +28033,1111 @@
             return false;
         }
     },
+    
+    getBoxCharacteristics: function(dx, dy) {
+        if (!this.boxCharacteristics) {
+            var xOffset = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+                "border-left-width")) + parseInt(OpenLayers.Element.getStyle(
+                this.zoomBox, "border-right-width")) + 1;
+            var yOffset = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+                "border-top-width")) + parseInt(OpenLayers.Element.getStyle(
+                this.zoomBox, "border-bottom-width")) + 1;
+            // all browsers use the new box model, except IE in quirks mode
+            var newBoxModel = OpenLayers.Util.getBrowserName() == "msie" ?
+                document.compatMode != "BackCompat" : true;
+            this.boxCharacteristics = {
+                xOffset: xOffset,
+                yOffset: yOffset,
+                newBoxModel: newBoxModel
+            }
+        }
+        return this.boxCharacteristics;
+    },
 
     CLASS_NAME: "OpenLayers.Handler.Box"
 });
 /* ======================================================================
+    OpenLayers/Handler/RegularPolygon.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.RegularPolygon
+ * Handler to draw a regular polygon on the map.  Polygon is displayed on mouse
+ *     down, moves or is modified on mouse move, and is finished on mouse up.
+ *     The handler triggers callbacks for 'done' and 'cancel'.  Create a new
+ *     instance with the <OpenLayers.Handler.RegularPolygon> constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {
+    
+    /**
+     * APIProperty: sides
+     * {Integer} Number of sides for the regular polygon.  Needs to be greater
+     *     than 2.  Defaults to 4.
+     */
+    sides: 4,
+
+    /**
+     * APIProperty: radius
+     * {Float} Optional radius in map units of the regular polygon.  If this is
+     *     set to some non-zero value, a polygon with a fixed radius will be
+     *     drawn and dragged with mose movements.  If this property is not
+     *     set, dragging changes the radius of the polygon.  Set to null by
+     *     default.
+     */
+    radius: null,
+    
+    /**
+     * APIProperty: snapAngle
+     * {Float} If set to a non-zero value, the handler will snap the polygon
+     *     rotation to multiples of the snapAngle.  Value is an angle measured
+     *     in degrees counterclockwise from the positive x-axis.  
+     */
+    snapAngle: null,
+    
+    /**
+     * APIProperty: snapToggle
+     * {String} If set, snapToggle is checked on mouse events and will set
+     *     the snap mode to the opposite of what it currently is.  To disallow
+     *     toggling between snap and non-snap mode, set freehandToggle to
+     *     null.  Acceptable toggle values are 'shiftKey', 'ctrlKey', and
+     *     'altKey'. Snap mode is only possible if this.snapAngle is set to a
+     *     non-zero value.
+     */
+    snapToggle: 'shiftKey',
+    
+    /**
+     * APIProperty: persist
+     * {Boolean} Leave the feature rendered until clear is called.  Default
+     *     is false.  If set to true, the feature remains rendered until
+     *     clear is called, typically by deactivating the handler or starting
+     *     another drawing.
+     */
+    persist: false,
+
+    /**
+     * APIProperty: irregular
+     * {Boolean} Draw an irregular polygon instead of a regular polygon.
+     *     Default is false.  If true, the initial mouse down will represent
+     *     one corner of the polygon bounds and with each mouse movement, the
+     *     polygon will be stretched so the opposite corner of its bounds
+     *     follows the mouse position.  This property takes precedence over
+     *     the radius property.  If set to true, the radius property will
+     *     be ignored.
+     */
+    irregular: false,
+
+    /**
+     * Property: angle
+     * {Float} The angle from the origin (mouse down) to the current mouse
+     *     position, in radians.  This is measured counterclockwise from the
+     *     positive x-axis.
+     */
+    angle: null,
+
+    /**
+     * Property: fixedRadius
+     * {Boolean} The polygon has a fixed radius.  True if a radius is set before
+     *     drawing begins.  False otherwise.
+     */
+    fixedRadius: false,
+
+    /**
+     * Property: feature
+     * {<OpenLayers.Feature.Vector>} The currently drawn polygon feature
+     */
+    feature: null,
+
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>} The temporary drawing layer
+     */
+    layer: null,
+
+    /**
+     * Property: origin
+     * {<OpenLayers.Geometry.Point>} Location of the first mouse down
+     */
+    origin: null,
+
+    /**
+     * Constructor: OpenLayers.Handler.RegularPolygon
+     * Create a new regular polygon handler.
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} The control that owns this handler
+     * callbacks - {Array} An object with a 'done' property whos value is a
+     *     function to be called when the polygon drawing is finished.
+     *     The callback should expect to recieve a single argument,
+     *     the polygon geometry.  If the callbacks object contains a
+     *     'cancel' property, this function will be called when the
+     *     handler is deactivated while drawing.  The cancel should
+     *     expect to receive a geometry.
+     * options - {Object} An object with properties to be set on the handler.
+     *     If the options.sides property is not specified, the number of sides
+     *     will default to 4.
+     */
+    initialize: function(control, callbacks, options) {
+        this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
+
+        OpenLayers.Handler.prototype.initialize.apply(this,
+                                                [control, callbacks, options]);
+        this.options = (options) ? options : new Object();
+    },
+    
+    /**
+     * APIMethod: setOptions
+     * 
+     * Parameters:
+     * newOptions - {Object} 
+     */
+    setOptions: function (newOptions) {
+        OpenLayers.Util.extend(this.options, newOptions);
+        OpenLayers.Util.extend(this, newOptions);
+    },
+    
+    /**
+     * APIMethod: activate
+     * Turn on the handler.
+     *
+     * Return:
+     * {Boolean} The handler was successfully activated
+     */
+    activate: function() {
+        var activated = false;
+        if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+            // create temporary vector layer for rendering geometry sketch
+            var options = {
+                displayInLayerSwitcher: false,
+                // indicate that the temp vector layer will never be out of range
+                // without this, resolution properties must be specified at the
+                // map-level for this temporary layer to init its resolutions
+                // correctly
+                calculateInRange: function() { return true; }
+            };
+            this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
+            this.map.addLayer(this.layer);
+            activated = true;
+        }
+        return activated;
+    },
+
+    /**
+     * APIMethod: deactivate
+     * Turn off the handler.
+     *
+     * Return:
+     * {Boolean} The handler was successfully deactivated
+     */
+    deactivate: function() {
+        var deactivated = false;
+        if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this, arguments)) {
+            // call the cancel callback if mid-drawing
+            if(this.dragging) {
+                this.cancel();
+            }
+            // If a layer's map property is set to null, it means that that
+            // layer isn't added to the map. Since we ourself added the layer
+            // to the map in activate(), we can assume that if this.layer.map
+            // is null it means that the layer has been destroyed (as a result
+            // of map.destroy() for example.
+            if (this.layer.map != null) {
+                this.layer.destroy(false);
+                if (this.feature) {
+                    this.feature.destroy();
+                }
+            }
+            this.layer = null;
+            this.feature = null;
+            deactivated = true;
+        }
+        return deactivated;
+    },
+    
+    /**
+     * Method: downFeature
+     * Start drawing a new feature
+     *
+     * Parameters:
+     * evt - {Event} The drag start event
+     */
+    down: function(evt) {
+        this.fixedRadius = !!(this.radius);
+        var maploc = this.map.getLonLatFromPixel(evt.xy);
+        this.origin = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
+        // create the new polygon
+        if(!this.fixedRadius || this.irregular) {
+            // smallest radius should not be less one pixel in map units
+            // VML doesn't behave well with smaller
+            this.radius = this.map.getResolution();
+        }
+        if(this.persist) {
+            this.clear();
+        }
+        this.feature = new OpenLayers.Feature.Vector();
+        this.createGeometry();
+        this.layer.addFeatures([this.feature], {silent: true});
+        this.layer.drawFeature(this.feature, this.style);
+    },
+    
+    /**
+     * Method: move
+     * Respond to drag move events
+     *
+     * Parameters:
+     * evt - {Evt} The move event
+     */
+    move: function(evt) {
+        var maploc = this.map.getLonLatFromPixel(evt.xy);
+        var point = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
+        if(this.irregular) {
+            var ry = Math.sqrt(2) * Math.abs(point.y - this.origin.y) / 2;
+            this.radius = Math.max(this.map.getResolution() / 2, ry);
+        } else if(this.fixedRadius) {
+            this.origin = point;
+        } else {
+            this.calculateAngle(point, evt);
+            this.radius = Math.max(this.map.getResolution() / 2,
+                                   point.distanceTo(this.origin));
+        }
+        this.modifyGeometry();
+        if(this.irregular) {
+            var dx = point.x - this.origin.x;
+            var dy = point.y - this.origin.y;
+            var ratio;
+            if(dy == 0) {
+                ratio = dx / (this.radius * Math.sqrt(2));
+            } else {
+                ratio = dx / dy;
+            }
+            this.feature.geometry.resize(1, this.origin, ratio);
+            this.feature.geometry.move(dx / 2, dy / 2);
+        }
+        this.layer.drawFeature(this.feature, this.style);
+    },
+
+    /**
+     * Method: up
+     * Finish drawing the feature
+     *
+     * Parameters:
+     * evt - {Event} The mouse up event
+     */
+    up: function(evt) {
+        this.finalize();
+    },
+
+    /**
+     * Method: out
+     * Finish drawing the feature.
+     *
+     * Parameters:
+     * evt - {Event} The mouse out event
+     */
+    out: function(evt) {
+        this.finalize();
+    },
+
+    /**
+     * Method: createGeometry
+     * Create the new polygon geometry.  This is called at the start of the
+     *     drag and at any point during the drag if the number of sides
+     *     changes.
+     */
+    createGeometry: function() {
+        this.angle = Math.PI * ((1/this.sides) - (1/2));
+        if(this.snapAngle) {
+            this.angle += this.snapAngle * (Math.PI / 180);
+        }
+        this.feature.geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
+            this.origin, this.radius, this.sides, this.snapAngle
+        );
+    },
+    
+    /**
+     * Method: modifyGeometry
+     * Modify the polygon geometry in place.
+     */
+    modifyGeometry: function() {
+        var angle, dx, dy, point;
+        var ring = this.feature.geometry.components[0];
+        // if the number of sides ever changes, create a new geometry
+        if(ring.components.length != (this.sides + 1)) {
+            this.createGeometry();
+             ring = this.feature.geometry.components[0];
+        }
+        for(var i=0; i<this.sides; ++i) {
+            point = ring.components[i];
+            angle = this.angle + (i * 2 * Math.PI / this.sides);
+            point.x = this.origin.x + (this.radius * Math.cos(angle));
+            point.y = this.origin.y + (this.radius * Math.sin(angle));
+            point.clearBounds();
+        }
+    },
+    
+    /**
+     * Method: calculateAngle
+     * Calculate the angle based on settings.
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>}
+     * evt - {Event}
+     */
+    calculateAngle: function(point, evt) {
+        var alpha = Math.atan2(point.y - this.origin.y,
+                               point.x - this.origin.x);
+        if(this.snapAngle && (this.snapToggle && !evt[this.snapToggle])) {
+            var snapAngleRad = (Math.PI / 180) * this.snapAngle;
+            this.angle = Math.round(alpha / snapAngleRad) * snapAngleRad;
+        } else {
+            this.angle = alpha;
+        }
+    },
+
+    /**
+     * APIMethod: cancel
+     * Finish the geometry and call the "cancel" callback.
+     */
+    cancel: function() {
+        // the polygon geometry gets cloned in the callback method
+        this.callback("cancel", null);
+        this.finalize();
+    },
+
+    /**
+     * Method: finalize
+     * Finish the geometry and call the "done" callback.
+     */
+    finalize: function() {
+        this.origin = null;
+        this.radius = this.options.radius;
+    },
+
+    /**
+     * APIMethod: clear
+     * Clear any rendered features on the temporary layer.  This is called
+     *     when the handler is deactivated, canceled, or done (unless persist
+     *     is true).
+     */
+    clear: function() {
+        this.layer.renderer.clear();
+        this.layer.destroyFeatures();
+    },
+    
+    /**
+     * Method: callback
+     * Trigger the control's named callback with the given arguments
+     *
+     * Parameters:
+     * name - {String} The key for the callback that is one of the properties
+     *     of the handler's callbacks object.
+     * args - {Array} An array of arguments with which to call the callback
+     *     (defined by the control).
+     */
+    callback: function (name, args) {
+        // override the callback method to always send the polygon geometry
+        if (this.callbacks[name]) {
+            this.callbacks[name].apply(this.control,
+                                       [this.feature.geometry.clone()]);
+        }
+        // since sketch features are added to the temporary layer
+        // they must be cleared here if done or cancel
+        if(!this.persist && (name == "done" || name == "cancel")) {
+            this.clear();
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.RegularPolygon"
+});
+/* ======================================================================
+    OpenLayers/Layer/EventPane.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.EventPane
+ * Base class for 3rd party layers.  Create a new event pane layer with the
+ * <OpenLayers.Layer.EventPane> constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.EventPane = OpenLayers.Class(OpenLayers.Layer, {
+    
+    /**
+     * APIProperty: smoothDragPan
+     * {Boolean} smoothDragPan determines whether non-public/internal API
+     *     methods are used for better performance while dragging EventPane 
+     *     layers. When not in sphericalMercator mode, the smoother dragging 
+     *     doesn't actually move north/south directly with the number of 
+     *     pixels moved, resulting in a slight offset when you drag your mouse 
+     *     north south with this option on. If this visual disparity bothers 
+     *     you, you should turn this option off, or use spherical mercator. 
+     *     Default is on.
+     */
+    smoothDragPan: true,
+
+    /**
+     * Property: isBaseLayer
+     * {Boolean} EventPaned layers are always base layers, by necessity.
+     */ 
+    isBaseLayer: true,
+
+    /**
+     * APIProperty: isFixed
+     * {Boolean} EventPaned layers are fixed by default.
+     */ 
+    isFixed: true,
+
+    /**
+     * Property: pane
+     * {DOMElement} A reference to the element that controls the events.
+     */
+    pane: null,
+
+
+    /**
+     * Property: mapObject
+     * {Object} This is the object which will be used to load the 3rd party library
+     * in the case of the google layer, this will be of type GMap, 
+     * in the case of the ve layer, this will be of type VEMap
+     */ 
+    mapObject: null,
+
+
+    /**
+     * Constructor: OpenLayers.Layer.EventPane
+     * Create a new event pane layer
+     *
+     * Parameters:
+     * name - {String}
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+        if (this.pane == null) {
+            this.pane = OpenLayers.Util.createDiv(this.div.id + "_EventPane");
+        }
+    },
+    
+    /**
+     * APIMethod: destroy
+     * Deconstruct this layer.
+     */
+    destroy: function() {
+        this.mapObject = null;
+        OpenLayers.Layer.prototype.destroy.apply(this, arguments); 
+    },
+
+    
+    /**
+     * Method: setMap
+     * Set the map property for the layer. This is done through an accessor
+     * so that subclasses can override this and take special action once 
+     * they have their map variable set. 
+     *
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    setMap: function(map) {
+        OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+        
+        this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
+        this.pane.style.display = this.div.style.display;
+        this.pane.style.width="100%";
+        this.pane.style.height="100%";
+        if (OpenLayers.Util.getBrowserName() == "msie") {
+            this.pane.style.background = 
+                "url(" + OpenLayers.Util.getImagesLocation() + "blank.gif)";
+        }
+
+        if (this.isFixed) {
+            this.map.viewPortDiv.appendChild(this.pane);
+        } else {
+            this.map.layerContainerDiv.appendChild(this.pane);
+        }
+
+        // once our layer has been added to the map, we can load it
+        this.loadMapObject();
+    
+        // if map didn't load, display warning
+        if (this.mapObject == null) {
+            this.loadWarningMessage();
+        }
+    },
+
+    /**
+     * APIMethod: removeMap
+     * On being removed from the map, we'll like to remove the invisible 'pane'
+     *     div that we added to it on creation. 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    removeMap: function(map) {
+        if (this.pane && this.pane.parentNode) {
+            this.pane.parentNode.removeChild(this.pane);
+            this.pane = null;
+        }
+        OpenLayers.Layer.prototype.removeMap.apply(this, arguments);
+    },
+  
+    /**
+     * Method: loadWarningMessage
+     * If we can't load the map lib, then display an error message to the 
+     *     user and tell them where to go for help.
+     * 
+     *     This function sets up the layout for the warning message. Each 3rd
+     *     party layer must implement its own getWarningHTML() function to 
+     *     provide the actual warning message.
+     */
+    loadWarningMessage:function() {
+
+        this.div.style.backgroundColor = "darkblue";
+
+        var viewSize = this.map.getSize();
+        
+        var msgW = Math.min(viewSize.w, 300);
+        var msgH = Math.min(viewSize.h, 200);
+        var size = new OpenLayers.Size(msgW, msgH);
+
+        var centerPx = new OpenLayers.Pixel(viewSize.w/2, viewSize.h/2);
+
+        var topLeft = centerPx.add(-size.w/2, -size.h/2);            
+
+        var div = OpenLayers.Util.createDiv(this.name + "_warning", 
+                                            topLeft, 
+                                            size,
+                                            null,
+                                            null,
+                                            null,
+                                            "auto");
+
+        div.style.padding = "7px";
+        div.style.backgroundColor = "yellow";
+
+        div.innerHTML = this.getWarningHTML();
+        this.div.appendChild(div);
+    },
+  
+    /** 
+     * Method: getWarningHTML
+     * To be implemented by subclasses.
+     * 
+     * Returns:
+     * {String} String with information on why layer is broken, how to get
+     *          it working.
+     */
+    getWarningHTML:function() {
+        //should be implemented by subclasses
+        return "";
+    },
+  
+    /**
+     * Method: display
+     * Set the display on the pane
+     *
+     * Parameters:
+     * display - {Boolean}
+     */
+    display: function(display) {
+        OpenLayers.Layer.prototype.display.apply(this, arguments);
+        this.pane.style.display = this.div.style.display;
+    },
+  
+    /**
+     * Method: setZIndex
+     * Set the z-index order for the pane.
+     * 
+     * Parameters:
+     * zIndex - {int}
+     */
+    setZIndex: function (zIndex) {
+        OpenLayers.Layer.prototype.setZIndex.apply(this, arguments);
+        this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
+    },
+
+    /**
+     * Method: moveTo
+     * Handle calls to move the layer.
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * zoomChanged - {Boolean}
+     * dragging - {Boolean}
+     */
+    moveTo:function(bounds, zoomChanged, dragging) {
+        OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+        if (this.mapObject != null) {
+
+            var newCenter = this.map.getCenter();
+            var newZoom = this.map.getZoom();
+
+            if (newCenter != null) {
+
+                var moOldCenter = this.getMapObjectCenter();
+                var oldCenter = this.getOLLonLatFromMapObjectLonLat(moOldCenter);
+
+                var moOldZoom = this.getMapObjectZoom();
+                var oldZoom= this.getOLZoomFromMapObjectZoom(moOldZoom);
+
+                if ( !(newCenter.equals(oldCenter)) || 
+                     !(newZoom == oldZoom) ) {
+
+                    if (dragging && this.dragPanMapObject && 
+                        this.smoothDragPan) {
+                        var oldPx = this.map.getViewPortPxFromLonLat(oldCenter);
+                        var newPx = this.map.getViewPortPxFromLonLat(newCenter);
+                        this.dragPanMapObject(newPx.x-oldPx.x, oldPx.y-newPx.y);
+                    } else {
+                        var center = this.getMapObjectLonLatFromOLLonLat(newCenter);
+                        var zoom = this.getMapObjectZoomFromOLZoom(newZoom);
+                        this.setMapObjectCenter(center, zoom, dragging);
+                    }
+                }
+            }
+        }
+    },
+
+
+  /********************************************************/
+  /*                                                      */
+  /*                 Baselayer Functions                  */
+  /*                                                      */
+  /********************************************************/
+
+    /**
+     * Method: getLonLatFromViewPortPx
+     * Get a map location from a pixel location
+     * 
+     * Parameters:
+     * viewPortPx - {<OpenLayers.Pixel>}
+     *
+     * Returns:
+     *  {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+     *  port OpenLayers.Pixel, translated into lon/lat by map lib
+     *  If the map lib is not loaded or not centered, returns null
+     */
+    getLonLatFromViewPortPx: function (viewPortPx) {
+        var lonlat = null;
+        if ( (this.mapObject != null) && 
+             (this.getMapObjectCenter() != null) ) {
+            var moPixel = this.getMapObjectPixelFromOLPixel(viewPortPx);
+            var moLonLat = this.getMapObjectLonLatFromMapObjectPixel(moPixel);
+            lonlat = this.getOLLonLatFromMapObjectLonLat(moLonLat);
+        }
+        return lonlat;
+    },
+
+ 
+    /**
+     * Method: getViewPortPxFromLonLat
+     * Get a pixel location from a map location
+     *
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>}
+     *
+     * Returns:
+     * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+     * OpenLayers.LonLat, translated into view port pixels by map lib
+     * If map lib is not loaded or not centered, returns null
+     */
+    getViewPortPxFromLonLat: function (lonlat) {
+        var viewPortPx = null;
+        if ( (this.mapObject != null) && 
+             (this.getMapObjectCenter() != null) ) {
+
+            var moLonLat = this.getMapObjectLonLatFromOLLonLat(lonlat);
+            var moPixel = this.getMapObjectPixelFromMapObjectLonLat(moLonLat);
+        
+            viewPortPx = this.getOLPixelFromMapObjectPixel(moPixel);
+        }
+        return viewPortPx;
+    },
+
+  /********************************************************/
+  /*                                                      */
+  /*               Translation Functions                  */
+  /*                                                      */
+  /*   The following functions translate Map Object and   */
+  /*            OL formats for Pixel, LonLat              */
+  /*                                                      */
+  /********************************************************/
+
+  //
+  // TRANSLATION: MapObject LatLng <-> OpenLayers.LonLat
+  //
+
+    /**
+     * Method: getOLLonLatFromMapObjectLonLat
+     * Get an OL style map location from a 3rd party style map location
+     *
+     * Parameters
+     * moLonLat - {Object}
+     * 
+     * Returns:
+     * {<OpenLayers.LonLat>} An OpenLayers.LonLat, translated from the passed in 
+     *          MapObject LonLat
+     *          Returns null if null value is passed in
+     */
+    getOLLonLatFromMapObjectLonLat: function(moLonLat) {
+        var olLonLat = null;
+        if (moLonLat != null) {
+            var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
+            var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
+            olLonLat = new OpenLayers.LonLat(lon, lat);
+        }
+        return olLonLat;
+    },
+
+    /**
+     * Method: getMapObjectLonLatFromOLLonLat
+     * Get a 3rd party map location from an OL map location.
+     *
+     * Parameters:
+     * olLonLat - {<OpenLayers.LonLat>}
+     * 
+     * Returns:
+     * {Object} A MapObject LonLat, translated from the passed in 
+     *          OpenLayers.LonLat
+     *          Returns null if null value is passed in
+     */
+    getMapObjectLonLatFromOLLonLat: function(olLonLat) {
+        var moLatLng = null;
+        if (olLonLat != null) {
+            moLatLng = this.getMapObjectLonLatFromLonLat(olLonLat.lon,
+                                                         olLonLat.lat);
+        }
+        return moLatLng;
+    },
+
+
+  //
+  // TRANSLATION: MapObject Pixel <-> OpenLayers.Pixel
+  //
+
+    /**
+     * Method: getOLPixelFromMapObjectPixel
+     * Get an OL pixel location from a 3rd party pixel location.
+     *
+     * Parameters:
+     * moPixel - {Object}
+     * 
+     * Returns:
+     * {<OpenLayers.Pixel>} An OpenLayers.Pixel, translated from the passed in 
+     *          MapObject Pixel
+     *          Returns null if null value is passed in
+     */
+    getOLPixelFromMapObjectPixel: function(moPixel) {
+        var olPixel = null;
+        if (moPixel != null) {
+            var x = this.getXFromMapObjectPixel(moPixel);
+            var y = this.getYFromMapObjectPixel(moPixel);
+            olPixel = new OpenLayers.Pixel(x, y);
+        }
+        return olPixel;
+    },
+
+    /**
+     * Method: getMapObjectPixelFromOLPixel
+     * Get a 3rd party pixel location from an OL pixel location
+     *
+     * Parameters:
+     * olPixel - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {Object} A MapObject Pixel, translated from the passed in 
+     *          OpenLayers.Pixel
+     *          Returns null if null value is passed in
+     */
+    getMapObjectPixelFromOLPixel: function(olPixel) {
+        var moPixel = null;
+        if (olPixel != null) {
+            moPixel = this.getMapObjectPixelFromXY(olPixel.x, olPixel.y);
+        }
+        return moPixel;
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.EventPane"
+});
+/* ======================================================================
+    OpenLayers/Layer/FixedZoomLevels.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.FixedZoomLevels
+ *   Some Layers will already have established zoom levels (like google 
+ *    or ve). Instead of trying to determine them and populate a resolutions[]
+ *    Array with those values, we will hijack the resolution functionality
+ *    here.
+ * 
+ *   When you subclass FixedZoomLevels: 
+ * 
+ *   The initResolutions() call gets nullified, meaning no resolutions[] array 
+ *    is set up. Which would be a big problem getResolution() in Layer, since 
+ *    it merely takes map.zoom and indexes into resolutions[]... but....
+ * 
+ *   The getResolution() call is also overridden. Instead of using the 
+ *    resolutions[] array, we simply calculate the current resolution based
+ *    on the current extent and the current map size. But how will we be able
+ *    to calculate the current extent without knowing the resolution...?
+ *  
+ *   The getExtent() function is also overridden. Instead of calculating extent
+ *    based on the center point and the current resolution, we instead 
+ *    calculate the extent by getting the lonlats at the top-left and 
+ *    bottom-right by using the getLonLatFromViewPortPx() translation function,
+ *    taken from the pixel locations (0,0) and the size of the map. But how 
+ *    will we be able to do lonlat-px translation without resolution....?
+ * 
+ *   The getZoomForResolution() method is overridden. Instead of indexing into
+ *    the resolutions[] array, we call OpenLayers.Layer.getExent(), passing in
+ *    the desired resolution. With this extent, we then call getZoomForExtent() 
+ * 
+ * 
+ *   Whenever you implement a layer using OpenLayers.Layer.FixedZoomLevels, 
+ *    it is your responsibility to provide the following three functions:
+ * 
+ *   - getLonLatFromViewPortPx
+ *   - getViewPortPxFromLonLat
+ *   - getZoomForExtent
+ * 
+ *  ...those three functions should generally be provided by any reasonable 
+ *  API that you might be working from.
+ *
+ */
+OpenLayers.Layer.FixedZoomLevels = OpenLayers.Class({
+      
+  /********************************************************/
+  /*                                                      */
+  /*                 Baselayer Functions                  */
+  /*                                                      */
+  /*    The following functions must all be implemented   */
+  /*                  by all base layers                  */
+  /*                                                      */
+  /********************************************************/
+    
+    /**
+     * Constructor: OpenLayers.Layer.FixedZoomLevels
+     * Create a new fixed zoom levels layer.
+     */
+    initialize: function() {
+        //this class is only just to add the following functions... 
+        // nothing to actually do here... but it is probably a good
+        // idea to have layers that use these functions call this 
+        // inititalize() anyways, in case at some point we decide we 
+        // do want to put some functionality or state in here. 
+    },
+    
+    /**
+     * Method: initResolutions
+     * Populate the resolutions array
+     */
+    initResolutions: function() {
+
+        var props = new Array('minZoomLevel', 'maxZoomLevel', 'numZoomLevels');
+          
+        for(var i=0, len=props.length; i<len; i++) {
+            var property = props[i];
+            this[property] = (this.options[property] != null)  
+                                     ? this.options[property] 
+                                     : this.map[property];
+        }
+
+        if ( (this.minZoomLevel == null) ||
+             (this.minZoomLevel < this.MIN_ZOOM_LEVEL) ){
+            this.minZoomLevel = this.MIN_ZOOM_LEVEL;
+        }        
+
+        var limitZoomLevels = this.MAX_ZOOM_LEVEL - this.minZoomLevel + 1;
+        if (this.numZoomLevels != null) {
+            this.numZoomLevels = Math.min(this.numZoomLevels, limitZoomLevels);
+        } else {
+            if (this.maxZoomLevel != null) {
+                var zoomDiff = this.maxZoomLevel - this.minZoomLevel + 1;
+                this.numZoomLevels = Math.min(zoomDiff, limitZoomLevels);
+            } else {
+                this.numZoomLevels = limitZoomLevels;
+            }
+        }
+
+        this.maxZoomLevel = this.minZoomLevel + this.numZoomLevels - 1;
+
+        if (this.RESOLUTIONS != null) {
+            var resolutionsIndex = 0;
+            this.resolutions = [];
+            for(var i= this.minZoomLevel; i <= this.maxZoomLevel; i++) {
+                this.resolutions[resolutionsIndex++] = this.RESOLUTIONS[i];            
+            }
+            this.maxResolution = this.resolutions[0];
+            this.minResolution = this.resolutions[this.resolutions.length - 1];
+        }       
+    },
+    
+    /**
+     * APIMethod: getResolution
+     * Get the current map resolution
+     * 
+     * Returns:
+     * {Float} Map units per Pixel
+     */
+    getResolution: function() {
+
+        if (this.resolutions != null) {
+            return OpenLayers.Layer.prototype.getResolution.apply(this, arguments);
+        } else {
+            var resolution = null;
+            
+            var viewSize = this.map.getSize();
+            var extent = this.getExtent();
+            
+            if ((viewSize != null) && (extent != null)) {
+                resolution = Math.max( extent.getWidth()  / viewSize.w,
+                                       extent.getHeight() / viewSize.h );
+            }
+            return resolution;
+        }
+     },
+
+    /**
+     * APIMethod: getExtent
+     * Calculates using px-> lonlat translation functions on tl and br 
+     *     corners of viewport
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat 
+     *                       bounds of the current viewPort.
+     */
+    getExtent: function () {
+        var extent = null;
+        
+        
+        var size = this.map.getSize();
+        
+        var tlPx = new OpenLayers.Pixel(0,0);
+        var tlLL = this.getLonLatFromViewPortPx(tlPx);
+
+        var brPx = new OpenLayers.Pixel(size.w, size.h);
+        var brLL = this.getLonLatFromViewPortPx(brPx);
+        
+        if ((tlLL != null) && (brLL != null)) {
+            extent = new OpenLayers.Bounds(tlLL.lon, 
+                                       brLL.lat, 
+                                       brLL.lon, 
+                                       tlLL.lat);
+        }
+
+        return extent;
+    },
+
+    /**
+     * Method: getZoomForResolution
+     * Get the zoom level for a given resolution
+     *
+     * Parameters:
+     * resolution - {Float}
+     *
+     * Returns:
+     * {Integer} A suitable zoom level for the specified resolution.
+     *           If no baselayer is set, returns null.
+     */
+    getZoomForResolution: function(resolution) {
+      
+        if (this.resolutions != null) {
+            return OpenLayers.Layer.prototype.getZoomForResolution.apply(this, arguments);
+        } else {
+            var extent = OpenLayers.Layer.prototype.getExtent.apply(this, []);
+            return this.getZoomForExtent(extent);
+        }
+    },
+
+
+
+    
+    /********************************************************/
+    /*                                                      */
+    /*             Translation Functions                    */
+    /*                                                      */
+    /*    The following functions translate GMaps and OL    */ 
+    /*     formats for Pixel, LonLat, Bounds, and Zoom      */
+    /*                                                      */
+    /********************************************************/
+    
+    
+    //
+    // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom
+    //
+  
+    /**
+     * Method: getOLZoomFromMapObjectZoom
+     * Get the OL zoom index from the map object zoom level
+     *
+     * Parameters:
+     * moZoom - {Integer}
+     * 
+     * Returns:
+     * {Integer} An OpenLayers Zoom level, translated from the passed in zoom
+     *           Returns null if null value is passed in
+     */
+    getOLZoomFromMapObjectZoom: function(moZoom) {
+        var zoom = null;
+        if (moZoom != null) {
+            zoom = moZoom - this.minZoomLevel;
+        }
+        return zoom;
+    },
+    
+    /**
+     * Method: getMapObjectZoomFromOLZoom
+     * Get the map object zoom level from the OL zoom level
+     *
+     * Parameters:
+     * olZoom - {Integer}
+     * 
+     * Returns:
+     * {Integer} A MapObject level, translated from the passed in olZoom
+     *           Returns null if null value is passed in
+     */
+    getMapObjectZoomFromOLZoom: function(olZoom) {
+        var zoom = null; 
+        if (olZoom != null) {
+            zoom = olZoom + this.minZoomLevel;
+        }
+        return zoom;
+    },
+
+    CLASS_NAME: "FixedZoomLevels.js"
+});
+
+/* ======================================================================
     OpenLayers/Layer/HTTPRequest.js
    ====================================================================== */
 
@@ -13769,7 +29303,7 @@
      */
     selectUrl: function(paramString, urls) {
         var product = 1;
-        for (var i = 0; i < paramString.length; i++) { 
+        for (var i=0, len=paramString.length; i<len; i++) { 
             product *= paramString.charCodeAt(i) * this.URL_HASH_FACTOR; 
             product -= Math.floor(product); 
         }
@@ -13848,6 +29382,1072 @@
     CLASS_NAME: "OpenLayers.Layer.HTTPRequest"
 });
 /* ======================================================================
+    OpenLayers/Layer/Image.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+ 
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Image
+ * Instances of OpenLayers.Layer.Image are used to display data from a web
+ * accessible image as a map layer.  Create a new image layer with the
+ * <OpenLayers.Layer.Image> constructor.  Inherits from <OpenLayers.Layer>.
+ */
+OpenLayers.Layer.Image = OpenLayers.Class(OpenLayers.Layer, {
+
+    /**
+     * Property: isBaseLayer
+     * {Boolean} The layer is a base layer.  Default is true.  Set this property
+     * in the layer options
+     */
+    isBaseLayer: true,
+    
+    /**
+     * Property: url
+     * {String} URL of the image to use
+     */
+    url: null,
+
+    /**
+     * Property: extent
+     * {<OpenLayers.Bounds>} The image bounds in map units
+     */
+    extent: null,
+    
+    /**
+     * Property: size
+     * {<OpenLayers.Size>} The image size in pixels
+     */
+    size: null,
+
+    /**
+     * Property: tile
+     * {<OpenLayers.Tile.Image>}
+     */
+    tile: null,
+
+    /**
+     * Property: aspectRatio
+     * {Float} The ratio of height/width represented by a single pixel in the
+     * graphic
+     */
+    aspectRatio: null,
+
+    /**
+     * Constructor: OpenLayers.Layer.Image
+     * Create a new image layer
+     *
+     * Parameters:
+     * name - {String} A name for the layer.
+     * url - {String} Relative or absolute path to the image
+     * extent - {<OpenLayers.Bounds>} The extent represented by the image
+     * size - {<OpenLayers.Size>} The size (in pixels) of the image
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, url, extent, size, options) {
+        this.url = url;
+        this.extent = extent;
+        this.size = size;
+        OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
+
+        this.aspectRatio = (this.extent.getHeight() / this.size.h) /
+                           (this.extent.getWidth() / this.size.w);
+    },    
+
+    /**
+     * Method: destroy
+     * Destroy this layer
+     */
+    destroy: function() {
+        if (this.tile) {
+            this.tile.destroy();
+            this.tile = null;
+        }
+        OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * Method: clone
+     * Create a clone of this layer
+     *
+     * Paramters:
+     * obj - {Object} An optional layer (is this ever used?)
+     *
+     * Returns:
+     * {<OpenLayers.Layer.Image>} An exact copy of this layer
+     */
+    clone: function(obj) {
+        
+        if(obj == null) {
+            obj = new OpenLayers.Layer.Image(this.name,
+                                               this.url,
+                                               this.extent,
+                                               this.size,
+                                               this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    },    
+    
+    /**
+     * APIMethod: setMap
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    setMap: function(map) {
+        /**
+         * If nothing to do with resolutions has been set, assume a single
+         * resolution determined by ratio*extent/size - if an image has a
+         * pixel aspect ratio different than one (as calculated above), the
+         * image will be stretched in one dimension only.
+         */
+        if( this.options.maxResolution == null ) {
+            this.options.maxResolution = this.aspectRatio *
+                                         this.extent.getWidth() /
+                                         this.size.w;
+        }
+        OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+    },
+
+    /** 
+     * Method: moveTo
+     * Create the tile for the image or resize it for the new resolution
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * zoomChanged - {Boolean}
+     * dragging - {Boolean}
+     */
+    moveTo:function(bounds, zoomChanged, dragging) {
+        OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+        var firstRendering = (this.tile == null);
+
+        if(zoomChanged || firstRendering) {
+
+            //determine new tile size
+            this.setTileSize();
+
+            //determine new position (upper left corner of new bounds)
+            var ul = new OpenLayers.LonLat(this.extent.left, this.extent.top);
+            var ulPx = this.map.getLayerPxFromLonLat(ul);
+
+            if(firstRendering) {
+                //create the new tile
+                this.tile = new OpenLayers.Tile.Image(this, ulPx, this.extent, 
+                                                      null, this.tileSize);
+            } else {
+                //just resize the tile and set it's new position
+                this.tile.size = this.tileSize.clone();
+                this.tile.position = ulPx.clone();
+            }
+            this.tile.draw();
+        }
+    }, 
+
+    /**
+     * Set the tile size based on the map size.
+     */
+    setTileSize: function() {
+        var tileWidth = this.extent.getWidth() / this.map.getResolution();
+        var tileHeight = this.extent.getHeight() / this.map.getResolution();
+        this.tileSize = new OpenLayers.Size(tileWidth, tileHeight);
+    },
+
+    /**
+     * APIMethod: setUrl
+     * 
+     * Parameters:
+     * newUrl - {String}
+     */
+    setUrl: function(newUrl) {
+        this.url = newUrl;
+        this.tile.draw();
+    },
+
+    /** 
+     * APIMethod: getURL
+     * The url we return is always the same (the image itself never changes)
+     *     so we can ignore the bounds parameter (it will always be the same, 
+     *     anyways) 
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     */
+    getURL: function(bounds) {
+        return this.url;
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Image"
+});
+/* ======================================================================
+    OpenLayers/Layer/Markers.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Markers
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer> 
+ */
+OpenLayers.Layer.Markers = OpenLayers.Class(OpenLayers.Layer, {
+    
+    /** 
+     * APIProperty: isBaseLayer 
+     * {Boolean} Markers layer is never a base layer.  
+     */
+    isBaseLayer: false,
+    
+    /** 
+     * Property: markers 
+     * {Array(<OpenLayers.Marker>)} internal marker list 
+     */
+    markers: null,
+
+
+    /** 
+     * Property: drawn 
+     * {Boolean} internal state of drawing. This is a workaround for the fact
+     * that the map does not call moveTo with a zoomChanged when the map is
+     * first starting up. This lets us catch the case where we have *never*
+     * drawn the layer, and draw it even if the zoom hasn't changed.
+     */
+    drawn: false,
+    
+    /**
+     * Constructor: OpenLayers.Layer.Markers 
+     * Create a Markers layer.
+     *
+     * Parameters:
+     * name - {String} 
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+        this.markers = [];
+    },
+    
+    /**
+     * APIMethod: destroy 
+     */
+    destroy: function() {
+        this.clearMarkers();
+        this.markers = null;
+        OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * APIMethod: setOpacity
+     * Sets the opacity for all the markers.
+     * 
+     * Parameter:
+     * opacity - {Float}
+     */
+    setOpacity: function(opacity) {
+        if (opacity != this.opacity) {
+            this.opacity = opacity;
+            for (var i=0, len=this.markers.length; i<len; i++) {
+                this.markers[i].setOpacity(this.opacity);
+            }
+        }
+    },
+
+    /** 
+     * Method: moveTo
+     *
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     * zoomChanged - {Boolean} 
+     * dragging - {Boolean} 
+     */
+    moveTo:function(bounds, zoomChanged, dragging) {
+        OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+        if (zoomChanged || !this.drawn) {
+            for(var i=0, len=this.markers.length; i<len; i++) {
+                this.drawMarker(this.markers[i]);
+            }
+            this.drawn = true;
+        }
+    },
+
+    /**
+     * APIMethod: addMarker
+     *
+     * Parameters:
+     * marker - {<OpenLayers.Marker>} 
+     */
+    addMarker: function(marker) {
+        this.markers.push(marker);
+
+        if (this.opacity != null) {
+            marker.setOpacity(this.opacity);
+        }
+
+        if (this.map && this.map.getExtent()) {
+            marker.map = this.map;
+            this.drawMarker(marker);
+        }
+    },
+
+    /**
+     * APIMethod: removeMarker
+     *
+     * Parameters:
+     * marker - {<OpenLayers.Marker>} 
+     */
+    removeMarker: function(marker) {
+        if (this.markers && this.markers.length) {
+            OpenLayers.Util.removeItem(this.markers, marker);
+            if ((marker.icon != null) && (marker.icon.imageDiv != null) &&
+                (marker.icon.imageDiv.parentNode == this.div) ) {
+                this.div.removeChild(marker.icon.imageDiv);    
+                marker.drawn = false;
+            }
+        }
+    },
+
+    /**
+     * Method: clearMarkers
+     * This method removes all markers from a layer. The markers are not
+     * destroyed by this function, but are removed from the list of markers.
+     */
+    clearMarkers: function() {
+        if (this.markers != null) {
+            while(this.markers.length > 0) {
+                this.removeMarker(this.markers[0]);
+            }
+        }
+    },
+
+    /** 
+     * Method: drawMarker
+     * Calculate the pixel location for the marker, create it, and 
+     *    add it to the layer's div
+     *
+     * Parameters:
+     * marker - {<OpenLayers.Marker>} 
+     */
+    drawMarker: function(marker) {
+        var px = this.map.getLayerPxFromLonLat(marker.lonlat);
+        if (px == null) {
+            marker.display(false);
+        } else {
+            var markerImg = marker.draw(px);
+            if (!marker.drawn) {
+                this.div.appendChild(markerImg);
+                marker.drawn = true;
+            }
+        }
+    },
+    
+    /** 
+     * APIMethod: getDataExtent
+     * Calculates the max extent which includes all of the markers.
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>}
+     */
+    getDataExtent: function () {
+        var maxExtent = null;
+        
+        if ( this.markers && (this.markers.length > 0)) {
+            var maxExtent = new OpenLayers.Bounds();
+            for(var i=0, len=this.markers.length; i<len; i++) {
+                var marker = this.markers[i];
+                maxExtent.extend(marker.lonlat);
+            }
+        }
+
+        return maxExtent;
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Markers"
+});
+/* ======================================================================
+    OpenLayers/Layer/SphericalMercator.js
+   ====================================================================== */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.SphericalMercator
+ * A mixin for layers that wraps up the pieces neccesary to have a coordinate
+ *     conversion for working with commercial APIs which use a spherical
+ *     mercator projection.  Using this layer as a base layer, additional
+ *     layers can be used as overlays if they are in the same projection.
+ *
+ * A layer is given properties of this object by setting the sphericalMercator
+ *     property to true.
+ *
+ * More projection information:
+ *  - http://spatialreference.org/ref/user/google-projection/
+ *
+ * Proj4 Text:
+ *     +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0
+ *     +k=1.0 +units=m +nadgrids=@null +no_defs
+ *
+ * WKT:
+ *     900913=PROJCS["WGS84 / Simple Mercator", GEOGCS["WGS 84",
+ *     DATUM["WGS_1984", SPHEROID["WGS_1984", 6378137.0, 298.257223563]], 
+ *     PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295], 
+ *     AXIS["Longitude", EAST], AXIS["Latitude", NORTH]],
+ *     PROJECTION["Mercator_1SP_Google"], 
+ *     PARAMETER["latitude_of_origin", 0.0], PARAMETER["central_meridian", 0.0], 
+ *     PARAMETER["scale_factor", 1.0], PARAMETER["false_easting", 0.0], 
+ *     PARAMETER["false_northing", 0.0], UNIT["m", 1.0], AXIS["x", EAST],
+ *     AXIS["y", NORTH], AUTHORITY["EPSG","900913"]]
+ */
+OpenLayers.Layer.SphericalMercator = {
+
+    /**
+     * Method: getExtent
+     * Get the map's extent.
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>} The map extent.
+     */
+    getExtent: function() {
+        var extent = null;
+        if (this.sphericalMercator) {
+            extent = this.map.calculateBounds();
+        } else {
+            extent = OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this);
+        }
+        return extent;
+    },
+
+    /** 
+     * Method: initMercatorParameters 
+     * Set up the mercator parameters on the layer: resolutions,
+     *     projection, units.
+     */
+    initMercatorParameters: function() {
+        // set up properties for Mercator - assume EPSG:900913
+        this.RESOLUTIONS = [];
+        var maxResolution = 156543.0339;
+        for(var zoom=0; zoom<=this.MAX_ZOOM_LEVEL; ++zoom) {
+            this.RESOLUTIONS[zoom] = maxResolution / Math.pow(2, zoom);
+        }
+        this.units = "m";
+        this.projection = "EPSG:900913";
+    },
+
+    /**
+     * APIMethod: forwardMercator
+     * Given a lon,lat in EPSG:4326, return a point in Spherical Mercator.
+     *
+     * Parameters:
+     * lon - {float} 
+     * lat - {float}
+     * 
+     * Returns:
+     * {<OpenLayers.LonLat>} The coordinates transformed to Mercator.
+     */
+    forwardMercator: function(lon, lat) {
+        var x = lon * 20037508.34 / 180;
+        var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
+
+        y = y * 20037508.34 / 180;
+        
+        return new OpenLayers.LonLat(x, y);
+    },
+
+    /**
+     * APIMethod: inverseMercator
+     * Given a x,y in Spherical Mercator, return a point in EPSG:4326.
+     *
+     * Parameters:
+     * x - {float} A map x in Spherical Mercator.
+     * y - {float} A map y in Spherical Mercator.
+     * 
+     * Returns:
+     * {<OpenLayers.LonLat>} The coordinates transformed to EPSG:4326.
+     */
+    inverseMercator: function(x, y) {
+
+        var lon = (x / 20037508.34) * 180;
+        var lat = (y / 20037508.34) * 180;
+
+        lat = 180/Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2);
+        
+        return new OpenLayers.LonLat(lon, lat);
+    },
+
+    /**
+     * Method: projectForward 
+     * Given an object with x and y properties in EPSG:4326, modify the x,y
+     * properties on the object to be the Spherical Mercator projected
+     * coordinates.
+     *
+     * Parameters:
+     * point - {Object} An object with x and y properties. 
+     * 
+     * Returns:
+     * {Object} The point, with the x and y properties transformed to spherical
+     * mercator.
+     */
+    projectForward: function(point) {
+        var lonlat = OpenLayers.Layer.SphericalMercator.forwardMercator(point.x, point.y);
+        point.x = lonlat.lon;
+        point.y = lonlat.lat;
+        return point;
+    },
+    
+    /**
+     * Method: projectInverse
+     * Given an object with x and y properties in Spherical Mercator, modify
+     * the x,y properties on the object to be the unprojected coordinates.
+     *
+     * Parameters:
+     * point - {Object} An object with x and y properties. 
+     * 
+     * Returns:
+     * {Object} The point, with the x and y properties transformed from
+     * spherical mercator to unprojected coordinates..
+     */
+    projectInverse: function(point) {
+        var lonlat = OpenLayers.Layer.SphericalMercator.inverseMercator(point.x, point.y);
+        point.x = lonlat.lon;
+        point.y = lonlat.lat;
+        return point;
+    }
+
+};
+
+/**
+ * Note: Two transforms declared
+ * Transforms from EPSG:4326 to EPSG:900913 and from EPSG:900913 to EPSG:4326
+ *     are set by this class.
+ */
+OpenLayers.Projection.addTransform("EPSG:4326", "EPSG:900913",
+    OpenLayers.Layer.SphericalMercator.projectForward);
+OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:4326",
+    OpenLayers.Layer.SphericalMercator.projectInverse);
+/* ======================================================================
+    OpenLayers/Control/DrawFeature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DrawFeature
+ * Draws features on a vector layer when active.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.DrawFeature = OpenLayers.Class(OpenLayers.Control, {
+    
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>}
+     */
+    layer: null,
+
+    /**
+     * Property: callbacks
+     * {Object} The functions that are sent to the handler for callback
+     */
+    callbacks: null,
+    
+    /**
+     * Constant: EVENT_TYPES
+     *
+     * Supported event types:
+     *  - *featureadded* Triggered when a feature is added
+     */
+    EVENT_TYPES: ["featureadded"],
+    
+    /**
+     * APIProperty: featureAdded
+     * {Function} Called after each feature is added
+     */
+    featureAdded: function() {},
+
+    /**
+     * APIProperty: handlerOptions
+     * {Object} Used to set non-default properties on the control's handler
+     */
+    handlerOptions: null,
+    
+    /**
+     * Constructor: OpenLayers.Control.DrawFeature
+     * 
+     * Parameters:
+     * layer - {<OpenLayers.Layer.Vector>} 
+     * handler - {<OpenLayers.Handler>} 
+     * options - {Object} 
+     */
+    initialize: function(layer, handler, options) {
+        
+        // concatenate events specific to vector with those from the base
+        this.EVENT_TYPES =
+            OpenLayers.Control.DrawFeature.prototype.EVENT_TYPES.concat(
+            OpenLayers.Control.prototype.EVENT_TYPES
+        );
+        
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.callbacks = OpenLayers.Util.extend({done: this.drawFeature},
+                                                this.callbacks);
+        this.layer = layer;
+        this.handler = new handler(this, this.callbacks, this.handlerOptions);
+    },
+
+    /**
+     * Method: drawFeature
+     */
+    drawFeature: function(geometry) {
+        var feature = new OpenLayers.Feature.Vector(geometry);
+        this.layer.addFeatures([feature]);
+        this.featureAdded(feature);
+        this.events.triggerEvent("featureadded",{feature : feature});
+    },
+
+    CLASS_NAME: "OpenLayers.Control.DrawFeature"
+});
+/* ======================================================================
+    OpenLayers/Control/SelectFeature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Handler/Feature.js
+ */
+
+/**
+ * Class: OpenLayers.Control.SelectFeature
+ * Selects vector features from a given layer on click or hover. 
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
+    
+    /**
+     * Property: multipleKey
+     * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+     *     the <multiple> property to true.  Default is null.
+     */
+    multipleKey: null,
+    
+    /**
+     * Property: toggleKey
+     * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+     *     the <toggle> property to true.  Default is null.
+     */
+    toggleKey: null,
+    
+    /**
+     * APIProperty: multiple
+     * {Boolean} Allow selection of multiple geometries.  Default is false.
+     */
+    multiple: false, 
+
+    /**
+     * APIProperty: clickout
+     * {Boolean} Unselect features when clicking outside any feature.
+     *     Default is true.
+     */
+    clickout: true,
+
+    /**
+     * APIProperty: toggle
+     * {Boolean} Unselect a selected feature on click.  Default is false.  Only
+     *     has meaning if hover is false.
+     */
+    toggle: false,
+
+    /**
+     * APIProperty: hover
+     * {Boolean} Select on mouse over and deselect on mouse out.  If true, this
+     * ignores clicks and only listens to mouse moves.
+     */
+    hover: false,
+    
+    /**
+     * APIProperty: box
+     * {Boolean} Allow feature selection by drawing a box.
+     */
+    box: false,
+    
+    /**
+     * APIProperty: onSelect 
+     * {Function} Optional function to be called when a feature is selected.
+     * The function should expect to be called with a feature.
+     */
+    onSelect: function() {},
+
+    /**
+     * APIProperty: onUnselect
+     * {Function} Optional function to be called when a feature is unselected.
+     *                  The function should expect to be called with a feature.
+     */
+    onUnselect: function() {},
+
+    /**
+     * APIProperty: geometryTypes
+     * {Array(String)} To restrict selecting to a limited set of geometry types,
+     *     send a list of strings corresponding to the geometry class names.
+     */
+    geometryTypes: null,
+
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>}
+     */
+    layer: null,
+    
+    /**
+     * APIProperty: callbacks
+     * {Object} The functions that are sent to the handlers.feature for callback
+     */
+    callbacks: null,
+    
+    /**
+     * APIProperty: selectStyle 
+     * {Object} Hash of styles
+     */
+    selectStyle: null,
+    
+    /**
+     * Property: renderIntent
+     * {String} key used to retrieve the select style from the layer's
+     * style map.
+     */
+    renderIntent: "select",
+
+    /**
+     * Property: handlers
+     * {Object} Object with references to multiple <OpenLayers.Handler>
+     *     instances.
+     */
+    handlers: null,
+
+    /**
+     * Constructor: <OpenLayers.Control.SelectFeature>
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.Vector>} 
+     * options - {Object} 
+     */
+    initialize: function(layer, options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.layer = layer;
+        var callbacks = {
+            click: this.clickFeature,
+            clickout: this.clickoutFeature
+        };
+        if (this.hover) {
+            callbacks.over = this.overFeature;
+            callbacks.out = this.outFeature;
+        }
+             
+        this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
+        this.handlers = {
+            feature: new OpenLayers.Handler.Feature(
+                this, layer, this.callbacks, {geometryTypes: this.geometryTypes}
+            )
+        };
+
+        if (this.box) {
+            this.handlers.box = new OpenLayers.Handler.Box(
+                this, {done: this.selectBox},
+                {boxDivClassName: "olHandlerBoxSelectFeature"}
+            ); 
+        }
+    },
+
+    /**
+     * Method: activate
+     * Activates the control.
+     * 
+     * Returns:
+     * {Boolean} The control was effectively activated.
+     */
+    activate: function () {
+        if (!this.active) {
+            this.handlers.feature.activate();
+            if(this.box && this.handlers.box) {
+                this.handlers.box.activate();
+            }
+        }
+        return OpenLayers.Control.prototype.activate.apply(
+            this, arguments
+        );
+    },
+
+    /**
+     * Method: deactivate
+     * Deactivates the control.
+     * 
+     * Returns:
+     * {Boolean} The control was effectively deactivated.
+     */
+    deactivate: function () {
+        if (this.active) {
+            this.handlers.feature.deactivate();
+            if(this.handlers.box) {
+                this.handlers.box.deactivate();
+            }
+        }
+        return OpenLayers.Control.prototype.deactivate.apply(
+            this, arguments
+        );
+    },
+
+    /**
+     * Method: unselectAll
+     * Unselect all selected features.  To unselect all except for a single
+     *     feature, set the options.except property to the feature.
+     *
+     * Parameters:
+     * options - {Object} Optional configuration object.
+     */
+    unselectAll: function(options) {
+        // we'll want an option to supress notification here
+        var feature;
+        for(var i=this.layer.selectedFeatures.length-1; i>=0; --i) {
+            feature = this.layer.selectedFeatures[i];
+            if(!options || options.except != feature) {
+                this.unselect(feature);
+            }
+        }
+    },
+
+    /**
+     * Method: clickFeature
+     * Called on click in a feature
+     * Only responds if this.hover is false.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    clickFeature: function(feature) {
+        if(!this.hover) {
+            var selected = (OpenLayers.Util.indexOf(this.layer.selectedFeatures,
+                                                    feature) > -1);
+            if(selected) {
+                if(this.toggleSelect()) {
+                    this.unselect(feature);
+                } else if(!this.multipleSelect()) {
+                    this.unselectAll({except: feature});
+                }
+            } else {
+                if(!this.multipleSelect()) {
+                    this.unselectAll({except: feature});
+                }
+                this.select(feature);
+            }
+        }
+    },
+
+    /**
+     * Method: multipleSelect
+     * Allow for multiple selected features based on <multiple> property and
+     *     <multipleKey> event modifier.
+     *
+     * Returns:
+     * {Boolean} Allow for multiple selected features.
+     */
+    multipleSelect: function() {
+        return this.multiple || this.handlers.feature.evt[this.multipleKey];
+    },
+    
+    /**
+     * Method: toggleSelect
+     * Event should toggle the selected state of a feature based on <toggle>
+     *     property and <toggleKey> event modifier.
+     *
+     * Returns:
+     * {Boolean} Toggle the selected state of a feature.
+     */
+    toggleSelect: function() {
+        return this.toggle || this.handlers.feature.evt[this.toggleKey];
+    },
+
+    /**
+     * Method: clickoutFeature
+     * Called on click outside a previously clicked (selected) feature.
+     * Only responds if this.hover is false.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Vector.Feature>} 
+     */
+    clickoutFeature: function(feature) {
+        if(!this.hover && this.clickout) {
+            this.unselectAll();
+        }
+    },
+
+    /**
+     * Method: overFeature
+     * Called on over a feature.
+     * Only responds if this.hover is true.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    overFeature: function(feature) {
+        if(this.hover &&
+           (OpenLayers.Util.indexOf(this.layer.selectedFeatures, feature) == -1)) {
+            this.select(feature);
+        }
+    },
+
+    /**
+     * Method: outFeature
+     * Called on out of a selected feature.
+     * Only responds if this.hover is true.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    outFeature: function(feature) {
+        if(this.hover) {
+            this.unselect(feature);
+        }
+    },
+    
+    /**
+     * Method: select
+     * Add feature to the layer's selectedFeature array, render the feature as
+     * selected, and call the onSelect function.
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    select: function(feature) {
+        var cont = this.layer.events.triggerEvent("beforefeatureselected", {
+            feature: feature
+        });
+        if(cont !== false) {
+            this.layer.selectedFeatures.push(feature);
+    
+            var selectStyle = this.selectStyle || this.renderIntent;
+            
+            this.layer.drawFeature(feature, selectStyle);
+            this.layer.events.triggerEvent("featureselected", {feature: feature});
+            this.onSelect(feature);
+        }
+    },
+
+    /**
+     * Method: unselect
+     * Remove feature from the layer's selectedFeature array, render the feature as
+     * normal, and call the onUnselect function.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     */
+    unselect: function(feature) {
+        // Store feature style for restoration later
+        this.layer.drawFeature(feature, "default");
+        OpenLayers.Util.removeItem(this.layer.selectedFeatures, feature);
+        this.layer.events.triggerEvent("featureunselected", {feature: feature});
+        this.onUnselect(feature);
+    },
+    
+    /**
+     * Method: selectBox
+     * Callback from the handlers.box set up when <box> selection is true
+     *     on.
+     *
+     * Parameters:
+     * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }  
+     */
+    selectBox: function(position) {
+        if (position instanceof OpenLayers.Bounds) {
+            var minXY = this.map.getLonLatFromPixel(
+                new OpenLayers.Pixel(position.left, position.bottom)
+            );
+            var maxXY = this.map.getLonLatFromPixel(
+                new OpenLayers.Pixel(position.right, position.top)
+            );
+            var bounds = new OpenLayers.Bounds(
+                minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
+            );
+            
+            // if multiple is false, first deselect currently selected features
+            if (!this.multipleSelect()) {
+                this.unselectAll();
+            }
+            
+            // because we're using a box, we consider we want multiple selection
+            var prevMultiple = this.multiple;
+            this.multiple = true;
+            for(var i=0, len = this.layer.features.length; i<len; ++i) {
+                var feature = this.layer.features[i];
+                if (this.geometryTypes == null || OpenLayers.Util.indexOf(
+                        this.geometryTypes, feature.geometry.CLASS_NAME) > -1) {
+                    if (bounds.toGeometry().intersects(feature.geometry)) {
+                        if (OpenLayers.Util.indexOf(this.layer.selectedFeatures, feature) == -1) {
+                            this.select(feature);
+                        }
+                    }
+                }
+            }
+            this.multiple = prevMultiple;
+        }
+    },
+
+    /** 
+     * Method: setMap
+     * Set the map property for the control. 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        this.handlers.feature.setMap(map);
+        if (this.box) {
+            this.handlers.box.setMap(map);
+        }
+        OpenLayers.Control.prototype.setMap.apply(this, arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Control.SelectFeature"
+});
+/* ======================================================================
     OpenLayers/Control/ZoomBox.js
    ====================================================================== */
 
@@ -13931,6 +30531,1260 @@
     CLASS_NAME: "OpenLayers.Control.ZoomBox"
 });
 /* ======================================================================
+    OpenLayers/Format/WKT.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WKT
+ * Class for reading and writing Well-Known Text.  Create a new instance
+ * with the <OpenLayers.Format.WKT> constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.WKT = OpenLayers.Class(OpenLayers.Format, {
+    
+    /**
+     * Constructor: OpenLayers.Format.WKT
+     * Create a new parser for WKT
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *           this instance
+     *
+     * Returns:
+     * {<OpenLayers.Format.WKT>} A new WKT parser.
+     */
+    initialize: function(options) {
+        this.regExes = {
+            'typeStr': /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,
+            'spaces': /\s+/,
+            'parenComma': /\)\s*,\s*\(/,
+            'doubleParenComma': /\)\s*\)\s*,\s*\(\s*\(/,  // can't use {2} here
+            'trimParens': /^\s*\(?(.*?)\)?\s*$/
+        };
+        OpenLayers.Format.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * Method: read
+     * Deserialize a WKT string and return a vector feature or an
+     * array of vector features.  Supports WKT for POINT, MULTIPOINT,
+     * LINESTRING, MULTILINESTRING, POLYGON, MULTIPOLYGON, and
+     * GEOMETRYCOLLECTION.
+     *
+     * Parameters:
+     * wkt - {String} A WKT string
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>|Array} A feature or array of features for
+     * GEOMETRYCOLLECTION WKT.
+     */
+    read: function(wkt) {
+        var features, type, str;
+        var matches = this.regExes.typeStr.exec(wkt);
+        if(matches) {
+            type = matches[1].toLowerCase();
+            str = matches[2];
+            if(this.parse[type]) {
+                features = this.parse[type].apply(this, [str]);
+            }
+            if (this.internalProjection && this.externalProjection) {
+                if (features && 
+                    features.CLASS_NAME == "OpenLayers.Feature.Vector") {
+                    features.geometry.transform(this.externalProjection,
+                                                this.internalProjection);
+                } else if (features &&
+                           type != "geometrycollection" &&
+                           typeof features == "object") {
+                    for (var i=0, len=features.length; i<len; i++) {
+                        var component = features[i];
+                        component.geometry.transform(this.externalProjection,
+                                                     this.internalProjection);
+                    }
+                }
+            }
+        }    
+        return features;
+    },
+
+    /**
+     * Method: write
+     * Serialize a feature or array of features into a WKT string.
+     *
+     * Parameters:
+     * features - {<OpenLayers.Feature.Vector>|Array} A feature or array of
+     *            features
+     *
+     * Returns:
+     * {String} The WKT string representation of the input geometries
+     */
+    write: function(features) {
+        var collection, geometry, type, data, isCollection;
+        if(features.constructor == Array) {
+            collection = features;
+            isCollection = true;
+        } else {
+            collection = [features];
+            isCollection = false;
+        }
+        var pieces = [];
+        if(isCollection) {
+            pieces.push('GEOMETRYCOLLECTION(');
+        }
+        for(var i=0, len=collection.length; i<len; ++i) {
+            if(isCollection && i>0) {
+                pieces.push(',');
+            }
+            geometry = collection[i].geometry;
+            type = geometry.CLASS_NAME.split('.')[2].toLowerCase();
+            if(!this.extract[type]) {
+                return null;
+            }
+            if (this.internalProjection && this.externalProjection) {
+                geometry = geometry.clone();
+                geometry.transform(this.internalProjection, 
+                                   this.externalProjection);
+            }                       
+            data = this.extract[type].apply(this, [geometry]);
+            pieces.push(type.toUpperCase() + '(' + data + ')');
+        }
+        if(isCollection) {
+            pieces.push(')');
+        }
+        return pieces.join('');
+    },
+    
+    /**
+     * Object with properties corresponding to the geometry types.
+     * Property values are functions that do the actual data extraction.
+     */
+    extract: {
+        /**
+         * Return a space delimited string of point coordinates.
+         * @param {<OpenLayers.Geometry.Point>} point
+         * @returns {String} A string of coordinates representing the point
+         */
+        'point': function(point) {
+            return point.x + ' ' + point.y;
+        },
+
+        /**
+         * Return a comma delimited string of point coordinates from a multipoint.
+         * @param {<OpenLayers.Geometry.MultiPoint>} multipoint
+         * @returns {String} A string of point coordinate strings representing
+         *                  the multipoint
+         */
+        'multipoint': function(multipoint) {
+            var array = [];
+            for(var i=0, len=multipoint.components.length; i<len; ++i) {
+                array.push(this.extract.point.apply(this, [multipoint.components[i]]));
+            }
+            return array.join(',');
+        },
+        
+        /**
+         * Return a comma delimited string of point coordinates from a line.
+         * @param {<OpenLayers.Geometry.LineString>} linestring
+         * @returns {String} A string of point coordinate strings representing
+         *                  the linestring
+         */
+        'linestring': function(linestring) {
+            var array = [];
+            for(var i=0, len=linestring.components.length; i<len; ++i) {
+                array.push(this.extract.point.apply(this, [linestring.components[i]]));
+            }
+            return array.join(',');
+        },
+
+        /**
+         * Return a comma delimited string of linestring strings from a multilinestring.
+         * @param {<OpenLayers.Geometry.MultiLineString>} multilinestring
+         * @returns {String} A string of of linestring strings representing
+         *                  the multilinestring
+         */
+        'multilinestring': function(multilinestring) {
+            var array = [];
+            for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+                array.push('(' +
+                           this.extract.linestring.apply(this, [multilinestring.components[i]]) +
+                           ')');
+            }
+            return array.join(',');
+        },
+        
+        /**
+         * Return a comma delimited string of linear ring arrays from a polygon.
+         * @param {<OpenLayers.Geometry.Polygon>} polygon
+         * @returns {String} An array of linear ring arrays representing the polygon
+         */
+        'polygon': function(polygon) {
+            var array = [];
+            for(var i=0, len=polygon.components.length; i<len; ++i) {
+                array.push('(' +
+                           this.extract.linestring.apply(this, [polygon.components[i]]) +
+                           ')');
+            }
+            return array.join(',');
+        },
+
+        /**
+         * Return an array of polygon arrays from a multipolygon.
+         * @param {<OpenLayers.Geometry.MultiPolygon>} multipolygon
+         * @returns {Array} An array of polygon arrays representing
+         *                  the multipolygon
+         */
+        'multipolygon': function(multipolygon) {
+            var array = [];
+            for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+                array.push('(' +
+                           this.extract.polygon.apply(this, [multipolygon.components[i]]) +
+                           ')');
+            }
+            return array.join(',');
+        }
+
+    },
+
+    /**
+     * Object with properties corresponding to the geometry types.
+     * Property values are functions that do the actual parsing.
+     */
+    parse: {
+        /**
+         * Return point feature given a point WKT fragment.
+         * @param {String} str A WKT fragment representing the point
+         * @returns {<OpenLayers.Feature.Vector>} A point feature
+         * @private
+         */
+        'point': function(str) {
+            var coords = OpenLayers.String.trim(str).split(this.regExes.spaces);
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.Point(coords[0], coords[1])
+            );
+        },
+
+        /**
+         * Return a multipoint feature given a multipoint WKT fragment.
+         * @param {String} A WKT fragment representing the multipoint
+         * @returns {<OpenLayers.Feature.Vector>} A multipoint feature
+         * @private
+         */
+        'multipoint': function(str) {
+            var points = OpenLayers.String.trim(str).split(',');
+            var components = [];
+            for(var i=0, len=points.length; i<len; ++i) {
+                components.push(this.parse.point.apply(this, [points[i]]).geometry);
+            }
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.MultiPoint(components)
+            );
+        },
+        
+        /**
+         * Return a linestring feature given a linestring WKT fragment.
+         * @param {String} A WKT fragment representing the linestring
+         * @returns {<OpenLayers.Feature.Vector>} A linestring feature
+         * @private
+         */
+        'linestring': function(str) {
+            var points = OpenLayers.String.trim(str).split(',');
+            var components = [];
+            for(var i=0, len=points.length; i<len; ++i) {
+                components.push(this.parse.point.apply(this, [points[i]]).geometry);
+            }
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.LineString(components)
+            );
+        },
+
+        /**
+         * Return a multilinestring feature given a multilinestring WKT fragment.
+         * @param {String} A WKT fragment representing the multilinestring
+         * @returns {<OpenLayers.Feature.Vector>} A multilinestring feature
+         * @private
+         */
+        'multilinestring': function(str) {
+            var line;
+            var lines = OpenLayers.String.trim(str).split(this.regExes.parenComma);
+            var components = [];
+            for(var i=0, len=lines.length; i<len; ++i) {
+                line = lines[i].replace(this.regExes.trimParens, '$1');
+                components.push(this.parse.linestring.apply(this, [line]).geometry);
+            }
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.MultiLineString(components)
+            );
+        },
+        
+        /**
+         * Return a polygon feature given a polygon WKT fragment.
+         * @param {String} A WKT fragment representing the polygon
+         * @returns {<OpenLayers.Feature.Vector>} A polygon feature
+         * @private
+         */
+        'polygon': function(str) {
+            var ring, linestring, linearring;
+            var rings = OpenLayers.String.trim(str).split(this.regExes.parenComma);
+            var components = [];
+            for(var i=0, len=rings.length; i<len; ++i) {
+                ring = rings[i].replace(this.regExes.trimParens, '$1');
+                linestring = this.parse.linestring.apply(this, [ring]).geometry;
+                linearring = new OpenLayers.Geometry.LinearRing(linestring.components);
+                components.push(linearring);
+            }
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.Polygon(components)
+            );
+        },
+
+        /**
+         * Return a multipolygon feature given a multipolygon WKT fragment.
+         * @param {String} A WKT fragment representing the multipolygon
+         * @returns {<OpenLayers.Feature.Vector>} A multipolygon feature
+         * @private
+         */
+        'multipolygon': function(str) {
+            var polygon;
+            var polygons = OpenLayers.String.trim(str).split(this.regExes.doubleParenComma);
+            var components = [];
+            for(var i=0, len=polygons.length; i<len; ++i) {
+                polygon = polygons[i].replace(this.regExes.trimParens, '$1');
+                components.push(this.parse.polygon.apply(this, [polygon]).geometry);
+            }
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.MultiPolygon(components)
+            );
+        },
+
+        /**
+         * Return an array of features given a geometrycollection WKT fragment.
+         * @param {String} A WKT fragment representing the geometrycollection
+         * @returns {Array} An array of OpenLayers.Feature.Vector
+         * @private
+         */
+        'geometrycollection': function(str) {
+            // separate components of the collection with |
+            str = str.replace(/,\s*([A-Za-z])/g, '|$1');
+            var wktArray = OpenLayers.String.trim(str).split('|');
+            var components = [];
+            for(var i=0, len=wktArray.length; i<len; ++i) {
+                components.push(OpenLayers.Format.WKT.prototype.read.apply(this,[wktArray[i]]));
+            }
+            return components;
+        }
+
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WKT" 
+});     
+/* ======================================================================
+    OpenLayers/Layer/Boxes.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Layer/Markers.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Boxes
+ * Draw divs as 'boxes' on the layer. 
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.Boxes = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+    /**
+     * Constructor: OpenLayers.Layer.Boxes
+     *
+     * Parameters:
+     * name - {String} 
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function (name, options) {
+        OpenLayers.Layer.Markers.prototype.initialize.apply(this, arguments);
+    },
+    
+    /**
+     * Method: drawMarker 
+     * Calculate the pixel location for the marker, create it, and
+     *    add it to the layer's div
+     *
+     * Parameters: 
+     * marker - {<OpenLayers.Marker.Box>} 
+     */
+    drawMarker: function(marker) {
+        var bounds   = marker.bounds;
+        var topleft  = this.map.getLayerPxFromLonLat(
+                            new OpenLayers.LonLat(bounds.left,  bounds.top));
+        var botright = this.map.getLayerPxFromLonLat(
+                             new OpenLayers.LonLat(bounds.right, bounds.bottom));
+        if (botright == null || topleft == null) {
+            marker.display(false);
+        } else {
+            var sz = new OpenLayers.Size(
+                Math.max(1, botright.x - topleft.x),
+                Math.max(1, botright.y - topleft.y));
+            var markerDiv = marker.draw(topleft, sz);
+            if (!marker.drawn) {
+                this.div.appendChild(markerDiv);
+                marker.drawn = true;
+            }
+        }
+    },
+
+
+    /**
+     * APIMethod: removeMarker 
+     * 
+     * Parameters:
+     * marker - {<OpenLayers.Marker.Box>} 
+     */
+    removeMarker: function(marker) {
+        OpenLayers.Util.removeItem(this.markers, marker);
+        if ((marker.div != null) &&
+            (marker.div.parentNode == this.div) ) {
+            this.div.removeChild(marker.div);    
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Boxes"
+});
+/* ======================================================================
+    OpenLayers/Layer/GeoRSS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Markers.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.GeoRSS
+ * Add GeoRSS Point features to your map. 
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.Markers>
+ *  - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.GeoRSS = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+    /** 
+     * Property: location 
+     * {String} store url of text file 
+     */
+    location: null,
+
+    /** 
+     * Property: features 
+     * {Array(<OpenLayers.Feature>)} 
+     */
+    features: null,
+    
+    /**
+     * APIProperty: formatOptions
+     * {Object} Hash of options which should be passed to the format when it is
+     * created. Must be passed in the constructor.
+     */
+    formatOptions: null, 
+
+    /** 
+     * Property: selectedFeature 
+     * {<OpenLayers.Feature>} 
+     */
+    selectedFeature: null,
+
+    /** 
+     * APIProperty: icon 
+     * {<OpenLayers.Icon>}. This determines the Icon to be used on the map
+     * for this GeoRSS layer.
+     */
+    icon: null,
+
+    /**
+     * APIProperty: popupSize
+     * {<OpenLayers.Size>} This determines the size of GeoRSS popups. If 
+     * not provided, defaults to 250px by 120px. 
+     */
+    popupSize: null, 
+    
+    /** 
+     * APIProperty: useFeedTitle 
+     * {Boolean} Set layer.name to the first <title> element in the feed. Default is true. 
+     */
+    useFeedTitle: true,
+    
+    /**
+    * Constructor: OpenLayers.Layer.GeoRSS
+    * Create a GeoRSS Layer.
+    *
+    * Parameters:
+    * name - {String} 
+    * location - {String} 
+    * options - {Object}
+    */
+    initialize: function(name, location, options) {
+        OpenLayers.Layer.Markers.prototype.initialize.apply(this, [name, options]);
+        this.location = location;
+        this.features = [];
+    },
+
+    /**
+     * Method: destroy 
+     */
+    destroy: function() {
+        // Warning: Layer.Markers.destroy() must be called prior to calling
+        // clearFeatures() here, otherwise we leak memory. Indeed, if
+        // Layer.Markers.destroy() is called after clearFeatures(), it won't be
+        // able to remove the marker image elements from the layer's div since
+        // the markers will have been destroyed by clearFeatures().
+        OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+        this.clearFeatures();
+        this.features = null;
+    },
+
+    /**
+     * Method: loadRSS
+     * Start the load of the RSS data. Don't do this when we first add the layer,
+     * since we may not be visible at any point, and it would therefore be a waste.
+     */
+    loadRSS: function() {
+        if (!this.loaded) {
+            this.events.triggerEvent("loadstart");
+            OpenLayers.Request.GET({
+                url: this.location,
+                success: this.parseData,
+                scope: this
+            });
+            this.loaded = true;
+        }    
+    },    
+    
+    /**
+     * Method: moveTo
+     * If layer is visible and RSS has not been loaded, load RSS. 
+     * 
+     * Parameters:
+     * bounds - {Object} 
+     * zoomChanged - {Object} 
+     * minor - {Object} 
+     */
+    moveTo:function(bounds, zoomChanged, minor) {
+        OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+        if(this.visibility && !this.loaded){
+            this.loadRSS();
+        }
+    },
+        
+    /**
+     * Method: parseData
+     * Parse the data returned from the Events call.
+     *
+     * Parameters:
+     * ajaxRequest - {<OpenLayers.Request.XMLHttpRequest>} 
+     */
+    parseData: function(ajaxRequest) {
+        var doc = ajaxRequest.responseXML;
+        if (!doc || !doc.documentElement) {
+            doc = OpenLayers.Format.XML.prototype.read(ajaxRequest.responseText);
+        }
+        
+        if (this.useFeedTitle) {
+            var name = null;
+            try {
+                name = doc.getElementsByTagNameNS('*', 'title')[0].firstChild.nodeValue;
+            }
+            catch (e) {
+                name = doc.getElementsByTagName('title')[0].firstChild.nodeValue;
+            }
+            if (name) {
+                this.setName(name);
+            }    
+        }
+       
+        var options = {};
+        
+        OpenLayers.Util.extend(options, this.formatOptions);
+        
+        if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+            options.externalProjection = this.projection;
+            options.internalProjection = this.map.getProjectionObject();
+        }    
+        
+        var format = new OpenLayers.Format.GeoRSS(options);
+        var features = format.read(doc);
+        
+        for (var i=0, len=features.length; i<len; i++) {
+            var data = {};
+            var feature = features[i];
+            
+            // we don't support features with no geometry in the GeoRSS
+            // layer at this time. 
+            if (!feature.geometry) {
+                continue;
+            }    
+            
+            var title = feature.attributes.title ? 
+                         feature.attributes.title : "Untitled";
+            
+            var description = feature.attributes.description ? 
+                         feature.attributes.description : "No description.";
+            
+            var link = feature.attributes.link ? feature.attributes.link : "";
+
+            var location = feature.geometry.getBounds().getCenterLonLat();
+            
+            
+            data.icon = this.icon == null ? 
+                                     OpenLayers.Marker.defaultIcon() : 
+                                     this.icon.clone();
+            
+            data.popupSize = this.popupSize ? 
+                             this.popupSize.clone() :
+                             new OpenLayers.Size(250, 120);
+            
+            if (title || description) {
+                // we have supplemental data, store them.
+                data.title = title;
+                data.description = description;
+            
+                var contentHTML = '<div class="olLayerGeoRSSClose">[x]</div>'; 
+                contentHTML += '<div class="olLayerGeoRSSTitle">';
+                if (link) {
+                    contentHTML += '<a class="link" href="'+link+'" target="_blank">';
+                }
+                contentHTML += title;
+                if (link) {
+                    contentHTML += '</a>';
+                }
+                contentHTML += '</div>';
+                contentHTML += '<div style="" class="olLayerGeoRSSDescription">';
+                contentHTML += description;
+                contentHTML += '</div>';
+                data['popupContentHTML'] = contentHTML;                
+            }
+            var feature = new OpenLayers.Feature(this, location, data);
+            this.features.push(feature);
+            var marker = feature.createMarker();
+            marker.events.register('click', feature, this.markerClick);
+            this.addMarker(marker);
+        }
+        this.events.triggerEvent("loadend");
+    },
+    
+    /**
+     * Method: markerClick
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    markerClick: function(evt) {
+        var sameMarkerClicked = (this == this.layer.selectedFeature);
+        this.layer.selectedFeature = (!sameMarkerClicked) ? this : null;
+        for(var i=0, len=this.layer.map.popups.length; i<len; i++) {
+            this.layer.map.removePopup(this.layer.map.popups[i]);
+        }
+        if (!sameMarkerClicked) {
+            var popup = this.createPopup();
+            OpenLayers.Event.observe(popup.div, "click",
+                OpenLayers.Function.bind(function() { 
+                    for(var i=0, len=this.layer.map.popups.length; i<len; i++) { 
+                        this.layer.map.removePopup(this.layer.map.popups[i]); 
+                    }
+                }, this)
+            );
+            this.layer.map.addPopup(popup); 
+        }
+        OpenLayers.Event.stop(evt);
+    },
+
+    /**
+     * Method: clearFeatures
+     * Destroy all features in this layer.
+     */
+    clearFeatures: function() {
+        if (this.features != null) {
+            while(this.features.length > 0) {
+                var feature = this.features[0];
+                OpenLayers.Util.removeItem(this.features, feature);
+                feature.destroy();
+            }
+        }        
+    },
+    
+    CLASS_NAME: "OpenLayers.Layer.GeoRSS"
+});
+/* ======================================================================
+    OpenLayers/Layer/Google.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/SphericalMercator.js
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Google
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.SphericalMercator>
+ *  - <OpenLayers.Layer.EventPane>
+ *  - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.Google = OpenLayers.Class(
+    OpenLayers.Layer.EventPane, 
+    OpenLayers.Layer.FixedZoomLevels, {
+    
+    /** 
+     * Constant: MIN_ZOOM_LEVEL
+     * {Integer} 0 
+     */
+    MIN_ZOOM_LEVEL: 0,
+    
+    /** 
+     * Constant: MAX_ZOOM_LEVEL
+     * {Integer} 19
+     */
+    MAX_ZOOM_LEVEL: 19,
+
+    /** 
+     * Constant: RESOLUTIONS
+     * {Array(Float)} Hardcode these resolutions so that they are more closely
+     *                tied with the standard wms projection
+     */
+    RESOLUTIONS: [
+        1.40625, 
+        0.703125, 
+        0.3515625, 
+        0.17578125, 
+        0.087890625, 
+        0.0439453125,
+        0.02197265625, 
+        0.010986328125, 
+        0.0054931640625, 
+        0.00274658203125,
+        0.001373291015625, 
+        0.0006866455078125, 
+        0.00034332275390625,
+        0.000171661376953125, 
+        0.0000858306884765625, 
+        0.00004291534423828125,
+        0.00002145767211914062, 
+        0.00001072883605957031,
+        0.00000536441802978515, 
+        0.00000268220901489257
+    ],
+
+    /**
+     * APIProperty: type
+     * {GMapType}
+     */
+    type: null,
+
+    /**
+     * APIProperty: sphericalMercator
+     * {Boolean} Should the map act as a mercator-projected map? This will
+     *     cause all interactions with the map to be in the actual map 
+     *     projection, which allows support for vector drawing, overlaying 
+     *     other maps, etc. 
+     */
+    sphericalMercator: false, 
+    
+    /**
+     * Property: dragObject
+     * {GDraggableObject} Since 2.93, Google has exposed the ability to get
+     *     the maps GDraggableObject. We can now use this for smooth panning
+     */
+    dragObject: null, 
+
+    /** 
+     * Constructor: OpenLayers.Layer.Google
+     * 
+     * Parameters:
+     * name - {String} A name for the layer.
+     * options - {Object} An optional object whose properties will be set
+     *     on the layer.
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.EventPane.prototype.initialize.apply(this, arguments);
+        OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, 
+                                                                    arguments);
+        this.addContainerPxFunction();
+        if (this.sphericalMercator) {
+            OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+            this.initMercatorParameters();
+        }    
+    },
+    
+    /** 
+     * Method: loadMapObject
+     * Load the GMap and register appropriate event listeners. If we can't 
+     *     load GMap2, then display a warning message.
+     */
+    loadMapObject:function() {
+        
+        //has gmaps library has been loaded?
+        try {
+            // create GMap, hide nav controls
+            this.mapObject = new GMap2( this.div );
+            
+            //since v 2.93 getDragObject is now available.
+            if(typeof this.mapObject.getDragObject == "function") {
+                this.dragObject = this.mapObject.getDragObject();
+            } else {
+                this.dragPanMapObject = null;
+            }
+
+
+            // move the ToS and branding stuff up to the pane
+            // thanks a *mil* Erik for thinking of this
+            var poweredBy = this.div.lastChild;
+            this.div.removeChild(poweredBy);
+            this.pane.appendChild(poweredBy);
+            poweredBy.className = "olLayerGooglePoweredBy gmnoprint";
+            poweredBy.style.left = "";
+            poweredBy.style.bottom = "";
+
+            var termsOfUse = this.div.lastChild;
+            this.div.removeChild(termsOfUse);
+            this.pane.appendChild(termsOfUse);
+            termsOfUse.className = "olLayerGoogleCopyright";
+            termsOfUse.style.right = "";
+            termsOfUse.style.bottom = "";
+
+        } catch (e) {
+            OpenLayers.Console.error(e);
+        }
+               
+    },
+
+    /** 
+     * APIMethod: setMap
+     * Overridden from EventPane because if a map type has been specified, 
+     *     we need to attach a listener for the first moveend -- this is how 
+     *     we will know that the map has been centered. Only once the map has 
+     *     been centered is it safe to change the gmap object's map type. 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    setMap: function(map) {
+        OpenLayers.Layer.EventPane.prototype.setMap.apply(this, arguments);
+
+        if (this.type != null) {
+            this.map.events.register("moveend", this, this.setMapType);
+        }
+    },
+    
+    /** 
+     * Method: setMapType
+     * The map has been centered, and a map type was specified, so we 
+     *     set the map type on the gmap object, then unregister the listener
+     *     so that we dont keep doing this every time the map moves.
+     */
+    setMapType: function() {
+        if (this.mapObject.getCenter() != null) {
+            
+            // Support for custom map types.
+            if (OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),
+                                        this.type) == -1) {
+                this.mapObject.addMapType(this.type);
+            }    
+
+            this.mapObject.setMapType(this.type);
+            this.map.events.unregister("moveend", this, this.setMapType);
+        }
+    },
+
+    /**
+     * APIMethod: onMapResize
+     * 
+     * Parameters:
+     * evt - {Event}
+     */
+    onMapResize: function() {
+        if(this.visibility) {
+            this.mapObject.checkResize();  
+        } else {
+            this.windowResized = true;
+        }
+    },
+    
+    /**
+     * Method: display
+     * Hide or show the layer
+     *
+     * Parameters:
+     * display - {Boolean}
+     */
+    display: function(display) {
+        OpenLayers.Layer.EventPane.prototype.display.apply(this, arguments);
+        if(this.div.style.display == "block" && this.windowResized) {
+            this.mapObject.checkResize();
+            this.windowResized = false;
+        }
+    },
+
+    /**
+     * APIMethod: getZoomForExtent
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     *  
+     * Returns:
+     * {Integer} Corresponding zoom level for a specified Bounds. 
+     *           If mapObject is not loaded or not centered, returns null
+     *
+    getZoomForExtent: function (bounds) {
+        var zoom = null;
+        if (this.mapObject != null) {
+            var moBounds = this.getMapObjectBoundsFromOLBounds(bounds);
+            var moZoom = this.getMapObjectZoomFromMapObjectBounds(moBounds);
+
+            //make sure zoom is within bounds    
+            var moZoom = Math.min(Math.max(moZoom, this.minZoomLevel), 
+                                 this.maxZoomLevel);
+
+            zoom = this.getOLZoomFromMapObjectZoom(moZoom);
+        }
+        return zoom;
+    },
+    
+    */
+    
+  //
+  // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+  //
+
+    /**
+     * APIMethod: getOLBoundsFromMapObjectBounds
+     * 
+     * Parameters:
+     * moBounds - {Object}
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>} An <OpenLayers.Bounds>, translated from the 
+     *                       passed-in MapObject Bounds.
+     *                       Returns null if null value is passed in.
+     */
+    getOLBoundsFromMapObjectBounds: function(moBounds) {
+        var olBounds = null;
+        if (moBounds != null) {
+            var sw = moBounds.getSouthWest();
+            var ne = moBounds.getNorthEast();
+            if (this.sphericalMercator) {
+                sw = this.forwardMercator(sw.lng(), sw.lat());
+                ne = this.forwardMercator(ne.lng(), ne.lat());
+            } else {
+                sw = new OpenLayers.LonLat(sw.lng(), sw.lat()); 
+                ne = new OpenLayers.LonLat(ne.lng(), ne.lat()); 
+            }    
+            olBounds = new OpenLayers.Bounds(sw.lon, 
+                                             sw.lat, 
+                                             ne.lon, 
+                                             ne.lat );
+        }
+        return olBounds;
+    },
+
+    /**
+     * APIMethod: getMapObjectBoundsFromOLBounds
+     * 
+     * Parameters:
+     * olBounds - {<OpenLayers.Bounds>}
+     * 
+     * Returns:
+     * {Object} A MapObject Bounds, translated from olBounds
+     *          Returns null if null value is passed in
+     */
+    getMapObjectBoundsFromOLBounds: function(olBounds) {
+        var moBounds = null;
+        if (olBounds != null) {
+            var sw = this.sphericalMercator ? 
+              this.inverseMercator(olBounds.bottom, olBounds.left) : 
+              new OpenLayers.LonLat(olBounds.bottom, olBounds.left);
+            var ne = this.sphericalMercator ? 
+              this.inverseMercator(olBounds.top, olBounds.right) : 
+              new OpenLayers.LonLat(olBounds.top, olBounds.right);
+            moBounds = new GLatLngBounds(new GLatLng(sw.lat, sw.lon),
+                                         new GLatLng(ne.lat, ne.lon));
+        }
+        return moBounds;
+    },
+
+    /** 
+     * Method: addContainerPxFunction
+     * Hack-on function because GMAPS does not give it to us
+     * 
+     * Parameters: 
+     * gLatLng - {GLatLng}
+     * 
+     * Returns:
+     * {GPoint} A GPoint specifying gLatLng translated into "Container" coords
+     */
+    addContainerPxFunction: function() {
+        if ( (typeof GMap2 != "undefined") && 
+             !GMap2.prototype.fromLatLngToContainerPixel) {
+          
+            GMap2.prototype.fromLatLngToContainerPixel = function(gLatLng) {
+          
+                // first we translate into "DivPixel"
+                var gPoint = this.fromLatLngToDivPixel(gLatLng);
+      
+                // locate the sliding "Div" div
+                var div = this.getContainer().firstChild.firstChild;
+  
+                // adjust by the offset of "Div" and voila!
+                gPoint.x += div.offsetLeft;
+                gPoint.y += div.offsetTop;
+    
+                return gPoint;
+            };
+        }
+    },
+
+    /** 
+     * APIMethod: getWarningHTML
+     * 
+     * Returns: 
+     * {String} String with information on why layer is broken, how to get
+     *          it working.
+     */
+    getWarningHTML:function() {
+        return OpenLayers.i18n("googleWarning");
+    },
+
+
+    /************************************
+     *                                  *
+     *   MapObject Interface Controls   *
+     *                                  *
+     ************************************/
+
+
+  // Get&Set Center, Zoom
+
+    /** 
+     * APIMethod: setMapObjectCenter
+     * Set the mapObject to the specified center and zoom
+     * 
+     * Parameters:
+     * center - {Object} MapObject LonLat format
+     * zoom - {int} MapObject zoom format
+     */
+    setMapObjectCenter: function(center, zoom) {
+        this.mapObject.setCenter(center, zoom); 
+    },
+   
+    /**
+     * APIMethod: dragPanMapObject
+     * 
+     * Parameters:
+     * dX - {Integer}
+     * dY - {Integer}
+     */
+    dragPanMapObject: function(dX, dY) {
+        this.dragObject.moveBy(new GSize(-dX, dY));
+    },
+
+    /**
+     * APIMethod: getMapObjectCenter
+     * 
+     * Returns: 
+     * {Object} The mapObject's current center in Map Object format
+     */
+    getMapObjectCenter: function() {
+        return this.mapObject.getCenter();
+    },
+
+    /** 
+     * APIMethod: getMapObjectZoom
+     * 
+     * Returns:
+     * {Integer} The mapObject's current zoom, in Map Object format
+     */
+    getMapObjectZoom: function() {
+        return this.mapObject.getZoom();
+    },
+
+
+  // LonLat - Pixel Translation
+  
+    /**
+     * APIMethod: getMapObjectLonLatFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Object} MapObject LonLat translated from MapObject Pixel
+     */
+    getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+        return this.mapObject.fromContainerPixelToLatLng(moPixel);
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Object} MapObject Pixel transtlated from MapObject LonLat
+     */
+    getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+        return this.mapObject.fromLatLngToContainerPixel(moLonLat);
+    },
+
+  
+  // Bounds
+  
+    /** 
+     * APIMethod: getMapObjectZoomFromMapObjectBounds
+     * 
+     * Parameters:
+     * moBounds - {Object} MapObject Bounds format
+     * 
+     * Returns:
+     * {Object} MapObject Zoom for specified MapObject Bounds
+     */
+    getMapObjectZoomFromMapObjectBounds: function(moBounds) {
+        return this.mapObject.getBoundsZoomLevel(moBounds);
+    },
+
+    /************************************
+     *                                  *
+     *       MapObject Primitives       *
+     *                                  *
+     ************************************/
+
+
+  // LonLat
+    
+    /**
+     * APIMethod: getLongitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Longitude of the given MapObject LonLat
+     */
+    getLongitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+          this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lon :
+          moLonLat.lng();  
+    },
+
+    /**
+     * APIMethod: getLatitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Latitude of the given MapObject LonLat
+     */
+    getLatitudeFromMapObjectLonLat: function(moLonLat) {
+        var lat = this.sphericalMercator ? 
+          this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lat :
+          moLonLat.lat(); 
+        return lat;  
+    },
+    
+    /**
+     * APIMethod: getMapObjectLonLatFromLonLat
+     * 
+     * Parameters:
+     * lon - {Float}
+     * lat - {Float}
+     * 
+     * Returns:
+     * {Object} MapObject LonLat built from lon and lat params
+     */
+    getMapObjectLonLatFromLonLat: function(lon, lat) {
+        var gLatLng;
+        if(this.sphericalMercator) {
+            var lonlat = this.inverseMercator(lon, lat);
+            gLatLng = new GLatLng(lonlat.lat, lonlat.lon);
+        } else {
+            gLatLng = new GLatLng(lat, lon);
+        }
+        return gLatLng;
+    },
+
+  // Pixel
+    
+    /**
+     * APIMethod: getXFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} X value of the MapObject Pixel
+     */
+    getXFromMapObjectPixel: function(moPixel) {
+        return moPixel.x;
+    },
+
+    /**
+     * APIMethod: getYFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} Y value of the MapObject Pixel
+     */
+    getYFromMapObjectPixel: function(moPixel) {
+        return moPixel.y;
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromXY
+     * 
+     * Parameters:
+     * x - {Integer}
+     * y - {Integer}
+     * 
+     * Returns:
+     * {Object} MapObject Pixel from x and y parameters
+     */
+    getMapObjectPixelFromXY: function(x, y) {
+        return new GPoint(x, y);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Google"
+});
+/* ======================================================================
     OpenLayers/Layer/Grid.js
    ====================================================================== */
 
@@ -14037,9 +31891,9 @@
      */
     clearGrid:function() {
         if (this.grid) {
-            for(var iRow=0; iRow < this.grid.length; iRow++) {
+            for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
                 var row = this.grid[iRow];
-                for(var iCol=0; iCol < row.length; iCol++) {
+                for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
                     var tile = row[iCol];
                     this.removeTileMonitoringHooks(tile);
                     tile.destroy();
@@ -14289,7 +32143,7 @@
         var minCols = Math.ceil(viewSize.w/this.tileSize.w) +
                       Math.max(1, 2 * this.buffer);
         
-        var extent = this.map.getMaxExtent();
+        var extent = this.maxExtent;
         var resolution = this.map.getResolution();
         
         var tileLayout = this.calculateGridLayout(bounds, extent, resolution);
@@ -14430,7 +32284,7 @@
         } 
         
         // now we go through and draw the tiles in forward order
-        for(var i=0; i < tileQueue.length; i++) {
+        for(var i=0, len=tileQueue.length; i<len; i++) {
             var tile = tileQueue[i];
             tile.draw();
             //mark tile as unqueued for the next time (since tiles are reused)
@@ -14549,7 +32403,7 @@
 
         var row = (prepend) ? grid.pop() : grid.shift();
 
-        for (var i=0; i < modelRow.length; i++) {
+        for (var i=0, len=modelRow.length; i<len; i++) {
             var modelTile = modelRow[i];
             var bounds = modelTile.bounds.clone();
             var position = modelTile.position.clone();
@@ -14579,7 +32433,7 @@
         var resolution = this.map.getResolution();
         var deltaLon = resolution * deltaX;
 
-        for (var i=0; i<this.grid.length; i++) {
+        for (var i=0, len=this.grid.length; i<len; i++) {
             var row = this.grid[i];
             var modelTileIndex = (prepend) ? 0 : (row.length - 1);
             var modelTile = row[modelTileIndex];
@@ -14655,7 +32509,7 @@
      * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
      */
     getTileBounds: function(viewPortPx) {
-        var maxExtent = this.map.getMaxExtent();
+        var maxExtent = this.maxExtent;
         var resolution = this.getResolution();
         var tileMapWidth = resolution * this.tileSize.w;
         var tileMapHeight = resolution * this.tileSize.h;
@@ -14676,6 +32530,2379 @@
     CLASS_NAME: "OpenLayers.Layer.Grid"
 });
 /* ======================================================================
+    OpenLayers/Layer/MultiMap.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.MultiMap
+ * Note that MultiMap does not fully support the sphericalMercator
+ * option. See Ticket #953 for more details.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.EventPane>
+ *  - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.MultiMap = OpenLayers.Class(
+  OpenLayers.Layer.EventPane, OpenLayers.Layer.FixedZoomLevels, {
+    
+    /** 
+     * Constant: MIN_ZOOM_LEVEL
+     * {Integer} 1 
+     */
+    MIN_ZOOM_LEVEL: 1,
+    
+    /** 
+     * Constant: MAX_ZOOM_LEVEL
+     * {Integer} 17
+     */
+    MAX_ZOOM_LEVEL: 17,
+
+    /** 
+     * Constant: RESOLUTIONS
+     * {Array(Float)} Hardcode these resolutions so that they are more closely
+     *                tied with the standard wms projection
+     */
+    RESOLUTIONS: [
+        9, 
+        1.40625,
+        0.703125,
+        0.3515625,
+        0.17578125,
+        0.087890625,
+        0.0439453125,
+        0.02197265625,
+        0.010986328125,
+        0.0054931640625,
+        0.00274658203125,
+        0.001373291015625,
+        0.0006866455078125,
+        0.00034332275390625,
+        0.000171661376953125,
+        0.0000858306884765625,
+        0.00004291534423828125
+    ],
+
+    /**
+     * APIProperty: type
+     * {?}
+     */
+    type: null,
+
+    /** 
+     * Constructor: OpenLayers.Layer.MultiMap
+     * 
+     * Parameters:
+     * name - {String}
+     * options - {Object}
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.EventPane.prototype.initialize.apply(this, arguments);
+        OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, 
+                                                                    arguments);
+        if (this.sphericalMercator) {
+            OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+            this.initMercatorParameters();
+            this.RESOLUTIONS.unshift(10); 
+        }    
+    },
+    
+    /**
+     * Method: loadMapObject
+     */
+    loadMapObject:function() {
+        try { //crash proofing
+            this.mapObject = new MultimapViewer(this.div);
+        } catch (e) { }
+    },
+
+    /** 
+     * APIMethod: getWarningHTML
+     * 
+     * Returns: 
+     * {String} String with information on why layer is broken, how to get
+     *          it working.
+     */
+    getWarningHTML:function() {
+        return OpenLayers.i18n(
+            "getLayerWarning", {'layerType':"MM", 'layerLib':"MultiMap"}
+        );
+    },
+
+
+
+    /************************************
+     *                                  *
+     *   MapObject Interface Controls   *
+     *                                  *
+     ************************************/
+
+
+  // Get&Set Center, Zoom
+
+    /** 
+     * APIMethod: setMapObjectCenter
+     * Set the mapObject to the specified center and zoom
+     * 
+     * Parameters:
+     * center - {Object} MapObject LonLat format
+     * zoom - {int} MapObject zoom format
+     */
+    setMapObjectCenter: function(center, zoom) {
+        this.mapObject.goToPosition(center, zoom); 
+    },
+   
+    /**
+     * APIMethod: getMapObjectCenter
+     * 
+     * Returns: 
+     * {Object} The mapObject's current center in Map Object format
+     */
+    getMapObjectCenter: function() {
+        return this.mapObject.getCurrentPosition();
+    },
+
+    /** 
+     * APIMethod: getMapObjectZoom
+     * 
+     * Returns:
+     * {Integer} The mapObject's current zoom, in Map Object format
+     */
+    getMapObjectZoom: function() {
+        return this.mapObject.getZoomFactor();
+    },
+
+
+  // LonLat - Pixel Translation
+  
+    /**
+     * APIMethod: getMapObjectLonLatFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Object} MapObject LonLat translated from MapObject Pixel
+     */
+    getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+        moPixel.x = moPixel.x - (this.map.getSize().w/2);
+        moPixel.y = moPixel.y - (this.map.getSize().h/2);
+        return this.mapObject.getMapPositionAt(moPixel);
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Object} MapObject Pixel transtlated from MapObject LonLat
+     */
+    getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+        return this.mapObject.geoPosToContainerPixels(moLonLat);
+    },
+
+
+    /************************************
+     *                                  *
+     *       MapObject Primitives       *
+     *                                  *
+     ************************************/
+
+
+  // LonLat
+    
+    /**
+     * APIMethod: getLongitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Longitude of the given MapObject LonLat
+     */
+    getLongitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.lon, moLonLat.lat).lon :
+            moLonLat.lon;
+    },
+
+    /**
+     * APIMethod: getLatitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Latitude of the given MapObject LonLat
+     */
+    getLatitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.lon, moLonLat.lat).lat :
+            moLonLat.lat;
+    },
+
+    /**
+     * APIMethod: getMapObjectLonLatFromLonLat
+     * 
+     * Parameters:
+     * lon - {Float}
+     * lat - {Float}
+     * 
+     * Returns:
+     * {Object} MapObject LonLat built from lon and lat params
+     */
+    getMapObjectLonLatFromLonLat: function(lon, lat) {
+        var mmLatLon;
+        if(this.sphericalMercator) {
+            var lonlat = this.inverseMercator(lon, lat);
+            mmLatLon = new MMLatLon(lonlat.lat, lonlat.lon);
+        } else {
+            mmLatLon = new MMLatLon(lat, lon);
+        }
+        return mmLatLon;
+    },
+
+  // Pixel
+    
+    /**
+     * APIMethod: getXFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} X value of the MapObject Pixel
+     */
+    getXFromMapObjectPixel: function(moPixel) {
+        return moPixel.x;
+    },
+
+    /**
+     * APIMethod: getYFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} Y value of the MapObject Pixel
+     */
+    getYFromMapObjectPixel: function(moPixel) {
+        return moPixel.y;
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromXY
+     * 
+     * Parameters:
+     * x - {Integer}
+     * y - {Integer}
+     * 
+     * Returns:
+     * {Object} MapObject Pixel from x and y parameters
+     */
+    getMapObjectPixelFromXY: function(x, y) {
+        return new MMPoint(x, y);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.MultiMap"
+});
+/* ======================================================================
+    OpenLayers/Layer/Text.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Markers.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Text
+ * This layer creates markers given data in a text file.  The <location>
+ *     property of the layer (specified as a property of the options argument
+ *     in the <OpenLayers.Layer.Text> constructor) points to a tab delimited
+ *     file with data used to create markers.
+ *
+ * The first row of the data file should be a header line with the column names
+ *     of the data. Each column should be delimited by a tab space. The
+ *     possible columns are:
+ *      - *point* lat,lon of the point where a marker is to be placed
+ *      - *lat*  Latitude of the point where a marker is to be placed
+ *      - *lon*  Longitude of the point where a marker is to be placed
+ *      - *icon* or *image* URL of marker icon to use.
+ *      - *iconSize* Size of Icon to use.
+ *      - *iconOffset* Where the top-left corner of the icon is to be placed
+ *            relative to the latitude and longitude of the point.
+ *      - *title* The text of the 'title' is placed inside an 'h2' marker
+ *            inside a popup, which opens when the marker is clicked.
+ *      - *description* The text of the 'description' is placed below the h2
+ *            in the popup. this can be plain text or HTML.
+ *
+ * Example text file:
+ * (code)
+ * lat	lon	title	description	iconSize	iconOffset	icon
+ * 10	20	title	description	21,25		-10,-25		http://www.openlayers.org/dev/img/marker.png
+ * (end)
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.Text = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+    /**
+     * APIProperty: location 
+     * {String} URL of text file.  Must be specified in the "options" argument
+     *   of the constructor. Can not be changed once passed in. 
+     */
+    location:null,
+
+    /** 
+     * Property: features
+     * {Array(<OpenLayers.Feature>)} 
+     */
+    features: null,
+    
+    /**
+     * APIProperty: formatOptions
+     * {Object} Hash of options which should be passed to the format when it is
+     * created. Must be passed in the constructor.
+     */
+    formatOptions: null, 
+
+    /** 
+     * Property: selectedFeature
+     * {<OpenLayers.Feature>}
+     */
+    selectedFeature: null,
+
+    /**
+     * Constructor: OpenLayers.Layer.Text
+     * Create a text layer.
+     * 
+     * Parameters:
+     * name - {String} 
+     * options - {Object} Object with properties to be set on the layer.
+     *     Must include <location> property.
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.Markers.prototype.initialize.apply(this, arguments);
+        this.features = new Array();
+    },
+
+    /**
+     * APIMethod: destroy 
+     */
+    destroy: function() {
+        // Warning: Layer.Markers.destroy() must be called prior to calling
+        // clearFeatures() here, otherwise we leak memory. Indeed, if
+        // Layer.Markers.destroy() is called after clearFeatures(), it won't be
+        // able to remove the marker image elements from the layer's div since
+        // the markers will have been destroyed by clearFeatures().
+        OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+        this.clearFeatures();
+        this.features = null;
+    },
+    
+    /**
+     * Method: loadText
+     * Start the load of the Text data. Don't do this when we first add the layer,
+     * since we may not be visible at any point, and it would therefore be a waste.
+     */
+    loadText: function() {
+        if (!this.loaded) {
+            if (this.location != null) {
+
+                var onFail = function(e) {
+                    this.events.triggerEvent("loadend");
+                };
+
+                this.events.triggerEvent("loadstart");
+                OpenLayers.Request.GET({
+                    url: this.location,
+                    success: this.parseData,
+                    failure: onFail,
+                    scope: this
+                });
+                this.loaded = true;
+            }
+        }    
+    },    
+    
+    /**
+     * Method: moveTo
+     * If layer is visible and Text has not been loaded, load Text. 
+     * 
+     * Parameters:
+     * bounds - {Object} 
+     * zoomChanged - {Object} 
+     * minor - {Object} 
+     */
+    moveTo:function(bounds, zoomChanged, minor) {
+        OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+        if(this.visibility && !this.loaded){
+            this.loadText();
+        }
+    },
+    
+    /**
+     * Method: parseData
+     *
+     * Parameters:
+     * ajaxRequest - {<OpenLayers.Request.XMLHttpRequest>} 
+     */
+    parseData: function(ajaxRequest) {
+        var text = ajaxRequest.responseText;
+        
+        var options = {};
+        
+        OpenLayers.Util.extend(options, this.formatOptions);
+        
+        if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+            options.externalProjection = this.projection;
+            options.internalProjection = this.map.getProjectionObject();
+        }    
+        
+        var parser = new OpenLayers.Format.Text(options);
+        features = parser.read(text);
+        for (var i=0, len=features.length; i<len; i++) {
+            var data = {};
+            var feature = features[i];
+            var location;
+            var iconSize, iconOffset;
+            
+            location = new OpenLayers.LonLat(feature.geometry.x, 
+                                             feature.geometry.y);
+            
+            if (feature.style.graphicWidth 
+                && feature.style.graphicHeight) {
+                iconSize = new OpenLayers.Size(
+                    feature.style.graphicWidth,
+                    feature.style.graphicHeight);
+            }        
+            
+            // FIXME: At the moment, we only use this if we have an 
+            // externalGraphic, because icon has no setOffset API Method.
+            /**
+             * FIXME FIRST!!
+             * The Text format does all sorts of parseFloating
+             * The result of a parseFloat for a bogus string is NaN.  That
+             * means the three possible values here are undefined, NaN, or a
+             * number.  The previous check was an identity check for null.  This
+             * means it was failing for all undefined or NaN.  A slightly better
+             * check is for undefined.  An even better check is to see if the
+             * value is a number (see #1441).
+             */
+            if (feature.style.graphicXOffset !== undefined
+                && feature.style.graphicYOffset !== undefined) {
+                iconOffset = new OpenLayers.Pixel(
+                    feature.style.graphicXOffset, 
+                    feature.style.graphicYOffset);
+            }
+            
+            if (feature.style.externalGraphic != null) {
+                data.icon = new OpenLayers.Icon(feature.style.externalGraphic, 
+                                                iconSize, 
+                                                iconOffset);
+            } else {
+                data.icon = OpenLayers.Marker.defaultIcon();
+
+                //allows for the case where the image url is not 
+                // specified but the size is. use a default icon
+                // but change the size
+                if (iconSize != null) {
+                    data.icon.setSize(iconSize);
+                }
+            }
+            
+            if ((feature.attributes.title != null) 
+                && (feature.attributes.description != null)) {
+                data['popupContentHTML'] = 
+                    '<h2>'+feature.attributes.title+'</h2>' + 
+                    '<p>'+feature.attributes.description+'</p>';
+            }
+            
+            data['overflow'] = feature.attributes.overflow || "auto"; 
+            
+            var markerFeature = new OpenLayers.Feature(this, location, data);
+            this.features.push(markerFeature);
+            var marker = markerFeature.createMarker();
+            if ((feature.attributes.title != null) 
+                && (feature.attributes.description != null)) {
+              marker.events.register('click', markerFeature, this.markerClick);
+            }
+            this.addMarker(marker);
+        }
+        this.events.triggerEvent("loadend");
+    },
+    
+    /**
+     * Property: markerClick
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    markerClick: function(evt) {
+        var sameMarkerClicked = (this == this.layer.selectedFeature);
+        this.layer.selectedFeature = (!sameMarkerClicked) ? this : null;
+        for(var i=0, len=this.layer.map.popups.length; i<len; i++) {
+            this.layer.map.removePopup(this.layer.map.popups[i]);
+        }
+        if (!sameMarkerClicked) {
+            this.layer.map.addPopup(this.createPopup()); 
+        }
+        OpenLayers.Event.stop(evt);
+    },
+
+    /**
+     * Method: clearFeatures
+     */
+    clearFeatures: function() {
+        if (this.features != null) {
+            while(this.features.length > 0) {
+                var feature = this.features[0];
+                OpenLayers.Util.removeItem(this.features, feature);
+                feature.destroy();
+            }
+        }        
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Text"
+});
+/* ======================================================================
+    OpenLayers/Layer/VirtualEarth.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.VirtualEarth
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.EventPane>
+ *  - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.VirtualEarth = OpenLayers.Class(
+    OpenLayers.Layer.EventPane,
+    OpenLayers.Layer.FixedZoomLevels, {
+    
+    /** 
+     * Constant: MIN_ZOOM_LEVEL
+     * {Integer} 1 
+     */
+    MIN_ZOOM_LEVEL: 1,
+    
+    /** 
+     * Constant: MAX_ZOOM_LEVEL
+     * {Integer} 17
+     */
+    MAX_ZOOM_LEVEL: 17,
+
+    /** 
+     * Constant: RESOLUTIONS
+     * {Array(Float)} Hardcode these resolutions so that they are more closely
+     *                tied with the standard wms projection
+     */
+    RESOLUTIONS: [
+        1.40625, 
+        0.703125, 
+        0.3515625, 
+        0.17578125, 
+        0.087890625, 
+        0.0439453125,
+        0.02197265625, 
+        0.010986328125, 
+        0.0054931640625, 
+        0.00274658203125,
+        0.001373291015625, 
+        0.0006866455078125, 
+        0.00034332275390625, 
+        0.000171661376953125, 
+        0.0000858306884765625, 
+        0.00004291534423828125
+    ],
+
+    /**
+     * APIProperty: type
+     * {VEMapType}
+     */
+    type: null,
+
+    /**
+     * APIProperty: sphericalMercator
+     * {Boolean} Should the map act as a mercator-projected map? This will
+     *     cause all interactions with the map to be in the actual map
+     *     projection, which allows support for vector drawing, overlaying
+     *     other maps, etc. 
+     */
+    sphericalMercator: false, 
+
+    /** 
+     * Constructor: OpenLayers.Layer.VirtualEarth
+     * 
+     * Parameters:
+     * name - {String}
+     * options - {Object}
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.EventPane.prototype.initialize.apply(this, arguments);
+        OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, 
+                                                                    arguments);
+        if(this.sphericalMercator) {
+            OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+            this.initMercatorParameters();
+        }
+    },
+    
+    /**
+     * Method: loadMapObject
+     */
+    loadMapObject:function() {
+
+        // create div and set to same size as map
+        var veDiv = OpenLayers.Util.createDiv(this.name);
+        var sz = this.map.getSize();
+        veDiv.style.width = sz.w + "px";
+        veDiv.style.height = sz.h + "px";
+        this.div.appendChild(veDiv);
+
+        try { // crash prevention
+            this.mapObject = new VEMap(this.name);
+        } catch (e) { }
+
+        if (this.mapObject != null) {
+            try { // this is to catch a Mozilla bug without falling apart
+
+                // The fourth argument is whether the map is 'fixed' -- not 
+                // draggable. See: 
+                // http://blogs.msdn.com/virtualearth/archive/2007/09/28/locking-a-virtual-earth-map.aspx
+                //
+                this.mapObject.LoadMap(null, null, this.type, true);
+                this.mapObject.AttachEvent("onmousedown", function() {return true; });
+
+            } catch (e) { }
+            this.mapObject.HideDashboard();
+        }
+
+        //can we do smooth panning? this is an unpublished method, so we need 
+        // to be careful
+        if ( !this.mapObject ||
+             !this.mapObject.vemapcontrol ||
+             !this.mapObject.vemapcontrol.PanMap ||
+             (typeof this.mapObject.vemapcontrol.PanMap != "function")) {
+
+            this.dragPanMapObject = null;
+        }
+
+    },
+
+    /** 
+     * APIMethod: getWarningHTML
+     * 
+     * Returns: 
+     * {String} String with information on why layer is broken, how to get
+     *          it working.
+     */
+    getWarningHTML:function() {
+        return OpenLayers.i18n(
+            "getLayerWarning", {'layerType':'VE', 'layerLib':'VirtualEarth'}
+        );
+    },
+
+
+
+    /************************************
+     *                                  *
+     *   MapObject Interface Controls   *
+     *                                  *
+     ************************************/
+
+
+  // Get&Set Center, Zoom
+
+    /** 
+     * APIMethod: setMapObjectCenter
+     * Set the mapObject to the specified center and zoom
+     * 
+     * Parameters:
+     * center - {Object} MapObject LonLat format
+     * zoom - {int} MapObject zoom format
+     */
+    setMapObjectCenter: function(center, zoom) {
+        this.mapObject.SetCenterAndZoom(center, zoom); 
+    },
+   
+    /**
+     * APIMethod: getMapObjectCenter
+     * 
+     * Returns: 
+     * {Object} The mapObject's current center in Map Object format
+     */
+    getMapObjectCenter: function() {
+        return this.mapObject.GetCenter();
+    },
+
+    /**
+     * APIMethod: dragPanMapObject
+     * 
+     * Parameters:
+     * dX - {Integer}
+     * dY - {Integer}
+     */
+    dragPanMapObject: function(dX, dY) {
+        this.mapObject.vemapcontrol.PanMap(dX, -dY);
+    },
+
+    /** 
+     * APIMethod: getMapObjectZoom
+     * 
+     * Returns:
+     * {Integer} The mapObject's current zoom, in Map Object format
+     */
+    getMapObjectZoom: function() {
+        return this.mapObject.GetZoomLevel();
+    },
+
+
+  // LonLat - Pixel Translation
+  
+    /**
+     * APIMethod: getMapObjectLonLatFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Object} MapObject LonLat translated from MapObject Pixel
+     */
+    getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+        //the conditional here is to test if we are running the v6 of VE
+        return (typeof VEPixel != 'undefined') 
+            ? this.mapObject.PixelToLatLong(moPixel)
+            : this.mapObject.PixelToLatLong(moPixel.x, moPixel.y);
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Object} MapObject Pixel transtlated from MapObject LonLat
+     */
+    getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+        return this.mapObject.LatLongToPixel(moLonLat);
+    },
+
+
+    /************************************
+     *                                  *
+     *       MapObject Primitives       *
+     *                                  *
+     ************************************/
+
+
+  // LonLat
+    
+    /**
+     * APIMethod: getLongitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Longitude of the given MapObject LonLat
+     */
+    getLongitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.Longitude, moLonLat.Latitude).lon :
+            moLonLat.Longitude;
+    },
+
+    /**
+     * APIMethod: getLatitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Latitude of the given MapObject LonLat
+     */
+    getLatitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.Longitude, moLonLat.Latitude).lat :
+            moLonLat.Latitude;
+    },
+
+    /**
+     * APIMethod: getMapObjectLonLatFromLonLat
+     * 
+     * Parameters:
+     * lon - {Float}
+     * lat - {Float}
+     * 
+     * Returns:
+     * {Object} MapObject LonLat built from lon and lat params
+     */
+    getMapObjectLonLatFromLonLat: function(lon, lat) {
+        var veLatLong;
+        if(this.sphericalMercator) {
+            var lonlat = this.inverseMercator(lon, lat);
+            veLatLong = new VELatLong(lonlat.lat, lonlat.lon);
+        } else {
+            veLatLong = new VELatLong(lat, lon);
+        }
+        return veLatLong;
+    },
+
+  // Pixel
+    
+    /**
+     * APIMethod: getXFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} X value of the MapObject Pixel
+     */
+    getXFromMapObjectPixel: function(moPixel) {
+        return moPixel.x;
+    },
+
+    /**
+     * APIMethod: getYFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} Y value of the MapObject Pixel
+     */
+    getYFromMapObjectPixel: function(moPixel) {
+        return moPixel.y;
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromXY
+     * 
+     * Parameters:
+     * x - {Integer}
+     * y - {Integer}
+     * 
+     * Returns:
+     * {Object} MapObject Pixel from x and y parameters
+     */
+    getMapObjectPixelFromXY: function(x, y) {
+        //the conditional here is to test if we are running the v6 of VE
+        return (typeof VEPixel != 'undefined') ? new VEPixel(x, y)
+                         : new Msn.VE.Pixel(x, y);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.VirtualEarth"
+});
+/* ======================================================================
+    OpenLayers/Layer/Yahoo.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Yahoo
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.EventPane>
+ *  - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.Yahoo = OpenLayers.Class(
+  OpenLayers.Layer.EventPane, OpenLayers.Layer.FixedZoomLevels, {
+    
+    /** 
+     * Constant: MIN_ZOOM_LEVEL
+     * {Integer} 0 
+     */
+    MIN_ZOOM_LEVEL: 0,
+    
+    /** 
+     * Constant: MAX_ZOOM_LEVEL
+     * {Integer} 15
+     */
+    MAX_ZOOM_LEVEL: 15,
+
+    /** 
+     * Constant: RESOLUTIONS
+     * {Array(Float)} Hardcode these resolutions so that they are more closely
+     *                tied with the standard wms projection
+     */
+    RESOLUTIONS: [
+        1.40625, 
+        0.703125, 
+        0.3515625, 
+        0.17578125, 
+        0.087890625, 
+        0.0439453125,
+        0.02197265625, 
+        0.010986328125, 
+        0.0054931640625, 
+        0.00274658203125, 
+        0.001373291015625, 
+        0.0006866455078125, 
+        0.00034332275390625, 
+        0.000171661376953125, 
+        0.0000858306884765625, 
+        0.00004291534423828125
+    ],
+
+    /**
+     * APIProperty: type
+     * {YahooMapType}
+     */
+    type: null,
+    
+    /**
+     * APIProperty: sphericalMercator
+     * {Boolean} Should the map act as a mercator-projected map? This will
+     * cause all interactions with the map to be in the actual map projection,
+     * which allows support for vector drawing, overlaying other maps, etc. 
+     */
+    sphericalMercator: false, 
+
+    /** 
+     * Constructor: OpenLayers.Layer.Yahoo
+     * 
+     * Parameters:
+     * name - {String}
+     * options - {Object}
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.EventPane.prototype.initialize.apply(this, arguments);
+        OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, 
+                                                                    arguments);
+        if(this.sphericalMercator) {
+            OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+            this.initMercatorParameters();
+        }
+    },
+    
+    /**
+     * Method: loadMapObject
+     */
+    loadMapObject:function() {
+        try { //do not crash! 
+            var size = this.getMapObjectSizeFromOLSize(this.map.getSize());
+            this.mapObject = new YMap(this.div, this.type, size);
+            this.mapObject.disableKeyControls();
+            this.mapObject.disableDragMap();
+
+            //can we do smooth panning? (moveByXY is not an API function)
+            if ( !this.mapObject.moveByXY || 
+                 (typeof this.mapObject.moveByXY != "function" ) ) {
+
+                this.dragPanMapObject = null;
+            }                
+        } catch(e) {}
+    },
+
+    /**
+     * Method: onMapResize
+     * 
+     */
+    onMapResize: function() {
+        try {
+            var size = this.getMapObjectSizeFromOLSize(this.map.getSize());
+            this.mapObject.resizeTo(size);
+        } catch(e) {}     
+    },    
+    
+    
+    /** 
+     * APIMethod: setMap
+     * Overridden from EventPane because we need to remove this yahoo event
+     *     pane which prohibits our drag and drop, and we can only do this 
+     *     once the map has been loaded and centered.
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    setMap: function(map) {
+        OpenLayers.Layer.EventPane.prototype.setMap.apply(this, arguments);
+
+        this.map.events.register("moveend", this, this.fixYahooEventPane);
+    },
+
+    /** 
+     * Method: fixYahooEventPane
+     * The map has been centered, so the mysterious yahoo eventpane has been
+     *     added. we remove it so that it doesnt mess with *our* event pane.
+     */
+    fixYahooEventPane: function() {
+        var yahooEventPane = OpenLayers.Util.getElement("ygddfdiv");
+        if (yahooEventPane != null) {
+            if (yahooEventPane.parentNode != null) {
+                yahooEventPane.parentNode.removeChild(yahooEventPane);
+            }
+            this.map.events.unregister("moveend", this, 
+                                       this.fixYahooEventPane);
+        }
+    },
+
+    /** 
+     * APIMethod: getWarningHTML
+     * 
+     * Returns: 
+     * {String} String with information on why layer is broken, how to get
+     *          it working.
+     */
+    getWarningHTML:function() {
+        return OpenLayers.i18n(
+            "getLayerWarning", {'layerType':'Yahoo', 'layerLib':'Yahoo'}
+        );
+    },
+
+  /********************************************************/
+  /*                                                      */
+  /*             Translation Functions                    */
+  /*                                                      */
+  /*    The following functions translate GMaps and OL    */ 
+  /*     formats for Pixel, LonLat, Bounds, and Zoom      */
+  /*                                                      */
+  /********************************************************/
+
+
+  //
+  // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom
+  //
+  
+    /**
+     * APIMethod: getOLZoomFromMapObjectZoom
+     * 
+     * Parameters:
+     * gZoom - {Integer}
+     * 
+     * Returns:
+     * {Integer} An OpenLayers Zoom level, translated from the passed in gZoom
+     *           Returns null if null value is passed in.
+     */
+    getOLZoomFromMapObjectZoom: function(moZoom) {
+        var zoom = null;
+        if (moZoom != null) {
+            zoom = OpenLayers.Layer.FixedZoomLevels.prototype.getOLZoomFromMapObjectZoom.apply(this, [moZoom]);
+            zoom = 18 - zoom;
+        }
+        return zoom;
+    },
+    
+    /**
+     * APIMethod: getMapObjectZoomFromOLZoom
+     * 
+     * Parameters:
+     * olZoom - {Integer}
+     * 
+     * Returns:
+     * {Integer} A MapObject level, translated from the passed in olZoom
+     *           Returns null if null value is passed in
+     */
+    getMapObjectZoomFromOLZoom: function(olZoom) {
+        var zoom = null; 
+        if (olZoom != null) {
+            zoom = OpenLayers.Layer.FixedZoomLevels.prototype.getMapObjectZoomFromOLZoom.apply(this, [olZoom]);
+            zoom = 18 - zoom;
+        }
+        return zoom;
+    },
+
+    /************************************
+     *                                  *
+     *   MapObject Interface Controls   *
+     *                                  *
+     ************************************/
+
+
+  // Get&Set Center, Zoom
+
+    /** 
+     * APIMethod: setMapObjectCenter
+     * Set the mapObject to the specified center and zoom
+     * 
+     * Parameters:
+     * center - {Object} MapObject LonLat format
+     * zoom - {int} MapObject zoom format
+     */
+    setMapObjectCenter: function(center, zoom) {
+        this.mapObject.drawZoomAndCenter(center, zoom); 
+    },
+   
+    /**
+     * APIMethod: getMapObjectCenter
+     * 
+     * Returns: 
+     * {Object} The mapObject's current center in Map Object format
+     */
+    getMapObjectCenter: function() {
+        return this.mapObject.getCenterLatLon();
+    },
+
+    /**
+     * APIMethod: dragPanMapObject
+     * 
+     * Parameters:
+     * dX - {Integer}
+     * dY - {Integer}
+     */
+    dragPanMapObject: function(dX, dY) {
+        this.mapObject.moveByXY({
+            'x': -dX,
+            'y': dY
+        });
+    },
+    
+    /** 
+     * APIMethod: getMapObjectZoom
+     * 
+     * Returns:
+     * {Integer} The mapObject's current zoom, in Map Object format
+     */
+    getMapObjectZoom: function() {
+        return this.mapObject.getZoomLevel();
+    },
+
+
+  // LonLat - Pixel Translation
+  
+    /**
+     * APIMethod: getMapObjectLonLatFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Object} MapObject LonLat translated from MapObject Pixel
+     */
+    getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+        return this.mapObject.convertXYLatLon(moPixel);
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Object} MapObject Pixel transtlated from MapObject LonLat
+     */
+    getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+        return this.mapObject.convertLatLonXY(moLonLat);
+    },
+
+
+    /************************************
+     *                                  *
+     *       MapObject Primitives       *
+     *                                  *
+     ************************************/
+
+
+  // LonLat
+    
+    /**
+     * APIMethod: getLongitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Longitude of the given MapObject LonLat
+     */
+    getLongitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.Lon, moLonLat.Lat).lon :
+            moLonLat.Lon;
+    },
+
+    /**
+     * APIMethod: getLatitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Latitude of the given MapObject LonLat
+     */
+    getLatitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.Lon, moLonLat.Lat).lat :
+            moLonLat.Lat;
+    },
+
+    /**
+     * APIMethod: getMapObjectLonLatFromLonLat
+     * 
+     * Parameters:
+     * lon - {Float}
+     * lat - {Float}
+     * 
+     * Returns:
+     * {Object} MapObject LonLat built from lon and lat params
+     */
+    getMapObjectLonLatFromLonLat: function(lon, lat) {
+        var yLatLong;
+        if(this.sphericalMercator) {
+            var lonlat = this.inverseMercator(lon, lat);
+            yLatLong = new YGeoPoint(lonlat.lat, lonlat.lon);
+        } else {
+            yLatLong = new YGeoPoint(lat, lon);
+        }
+        return yLatLong;
+    },
+
+  // Pixel
+    
+    /**
+     * APIMethod: getXFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} X value of the MapObject Pixel
+     */
+    getXFromMapObjectPixel: function(moPixel) {
+        return moPixel.x;
+    },
+
+    /**
+     * APIMethod: getYFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} Y value of the MapObject Pixel
+     */
+    getYFromMapObjectPixel: function(moPixel) {
+        return moPixel.y;
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromXY
+     * 
+     * Parameters:
+     * x - {Integer}
+     * y - {Integer}
+     * 
+     * Returns:
+     * {Object} MapObject Pixel from x and y parameters
+     */
+    getMapObjectPixelFromXY: function(x, y) {
+        return new YCoordPoint(x, y);
+    },
+    
+  // Size
+  
+    /**
+     * APIMethod: getMapObjectSizeFromOLSize
+     * 
+     * Parameters:
+     * olSize - {<OpenLayers.Size>}
+     * 
+     * Returns:
+     * {Object} MapObject Size from olSize parameter
+     */
+    getMapObjectSizeFromOLSize: function(olSize) {
+        return new YSize(olSize.w, olSize.h);
+    },
+    
+    CLASS_NAME: "OpenLayers.Layer.Yahoo"
+});
+/* ======================================================================
+    OpenLayers/Style.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+  * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Style
+ * This class represents a UserStyle obtained
+ *     from a SLD, containing styling rules.
+ */
+OpenLayers.Style = OpenLayers.Class({
+
+    /**
+     * APIProperty: name
+     * {String}
+     */
+    name: null,
+    
+    /**
+     * Property: title
+     * {String} Title of this style (set if included in SLD)
+     */
+    title: null,
+    
+    /**
+     * Property: description
+     * {String} Description of this style (set if abstract is included in SLD)
+     */
+    description: null,
+
+    /**
+     * APIProperty: layerName
+     * {<String>} name of the layer that this style belongs to, usually
+     * according to the NamedLayer attribute of an SLD document.
+     */
+    layerName: null,
+    
+    /**
+     * APIProperty: isDefault
+     * {Boolean}
+     */
+    isDefault: false,
+     
+    /** 
+     * Property: rules 
+     * {Array(<OpenLayers.Rule>)}
+     */
+    rules: null,
+    
+    /**
+     * Property: context
+     * {Object} An optional object with properties that symbolizers' property
+     * values should be evaluated against. If no context is specified,
+     * feature.attributes will be used
+     */
+    context: null,
+
+    /**
+     * Property: defaultStyle
+     * {Object} hash of style properties to use as default for merging
+     * rule-based style symbolizers onto. If no rules are defined,
+     * createSymbolizer will return this style.
+     */
+    defaultStyle: null,
+    
+    /**
+     * Property: propertyStyles
+     * {Hash of Boolean} cache of style properties that need to be parsed for
+     * propertyNames. Property names are keys, values won't be used.
+     */
+    propertyStyles: null,
+    
+
+    /** 
+     * Constructor: OpenLayers.Style
+     * Creates a UserStyle.
+     *
+     * Parameters:
+     * style        - {Object} Optional hash of style properties that will be
+     *                used as default style for this style object. This style
+     *                applies if no rules are specified. Symbolizers defined in
+     *                rules will extend this default style.
+     * options      - {Object} An optional object with properties to set on the
+     *                userStyle
+     * 
+     * Return:
+     * {<OpenLayers.Style>}
+     */
+    initialize: function(style, options) {
+        this.rules = [];
+
+        // use the default style from OpenLayers.Feature.Vector if no style
+        // was given in the constructor
+        this.setDefaultStyle(style || 
+                OpenLayers.Feature.Vector.style["default"]);
+        
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /** 
+     * APIMethod: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+        for (var i=0, len=this.rules.length; i<len; i++) {
+            this.rules[i].destroy();
+            this.rules[i] = null;
+        }
+        this.rules = null;
+        this.defaultStyle = null;
+    },
+    
+    /**
+     * Method: createSymbolizer
+     * creates a style by applying all feature-dependent rules to the base
+     * style.
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature>} feature to evaluate rules for
+     * 
+     * Returns:
+     * {Object} symbolizer hash
+     */
+    createSymbolizer: function(feature) {
+        var style = this.createLiterals(
+            OpenLayers.Util.extend({}, this.defaultStyle), feature);
+        
+        var rules = this.rules;
+
+        var rule, context;
+        var elseRules = [];
+        var appliedRules = false;
+        for(var i=0, len=rules.length; i<len; i++) {
+            rule = rules[i];
+            // does the rule apply?
+            var applies = rule.evaluate(feature);
+            
+            if(applies) {
+                if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
+                    elseRules.push(rule);
+                } else {
+                    appliedRules = true;
+                    this.applySymbolizer(rule, style, feature);
+                }
+            }
+        }
+        
+        // if no other rules apply, apply the rules with else filters
+        if(appliedRules == false && elseRules.length > 0) {
+            appliedRules = true;
+            for(var i=0, len=elseRules.length; i<len; i++) {
+                this.applySymbolizer(elseRules[i], style, feature);
+            }
+        }
+
+        // don't display if there were rules but none applied
+        if(rules.length > 0 && appliedRules == false) {
+            style.display = "none";
+        } else {
+            style.display = "";
+        }
+        
+        return style;
+    },
+    
+    /**
+     * Method: applySymbolizer
+     *
+     * Parameters:
+     * rule - {OpenLayers.Rule}
+     * style - {Object}
+     * feature - {<OpenLayer.Feature.Vector>}
+     *
+     * Returns:
+     * {Object} A style with new symbolizer applied.
+     */
+    applySymbolizer: function(rule, style, feature) {
+        var symbolizerPrefix = feature.geometry ?
+                this.getSymbolizerPrefix(feature.geometry) :
+                OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
+
+        var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
+
+        // merge the style with the current style
+        return this.createLiterals(
+                OpenLayers.Util.extend(style, symbolizer), feature);
+    },
+    
+    /**
+     * Method: createLiterals
+     * creates literals for all style properties that have an entry in
+     * <this.propertyStyles>.
+     * 
+     * Parameters:
+     * style   - {Object} style to create literals for. Will be modified
+     *           inline.
+     * feature - {Object}
+     * 
+     * Returns:
+     * {Object} the modified style
+     */
+    createLiterals: function(style, feature) {
+        var context = this.context || feature.attributes || feature.data;
+        
+        for (var i in this.propertyStyles) {
+            style[i] = OpenLayers.Style.createLiteral(style[i], context, feature);
+        }
+        return style;
+    },
+    
+    /**
+     * Method: findPropertyStyles
+     * Looks into all rules for this style and the defaultStyle to collect
+     * all the style hash property names containing ${...} strings that have
+     * to be replaced using the createLiteral method before returning them.
+     * 
+     * Returns:
+     * {Object} hash of property names that need createLiteral parsing. The
+     * name of the property is the key, and the value is true;
+     */
+    findPropertyStyles: function() {
+        var propertyStyles = {};
+
+        // check the default style
+        var style = this.defaultStyle;
+        this.addPropertyStyles(propertyStyles, style);
+
+        // walk through all rules to check for properties in their symbolizer
+        var rules = this.rules;
+        var symbolizer, value;
+        for (var i=0, len=rules.length; i<len; i++) {
+            var symbolizer = rules[i].symbolizer;
+            for (var key in symbolizer) {
+                value = symbolizer[key];
+                if (typeof value == "object") {
+                    // symbolizer key is "Point", "Line" or "Polygon"
+                    this.addPropertyStyles(propertyStyles, value);
+                } else {
+                    // symbolizer is a hash of style properties
+                    this.addPropertyStyles(propertyStyles, symbolizer);
+                    break;
+                }
+            }
+        }
+        return propertyStyles;
+    },
+    
+    /**
+     * Method: addPropertyStyles
+     * 
+     * Parameters:
+     * propertyStyles - {Object} hash to add new property styles to. Will be
+     *                  modified inline
+     * symbolizer     - {Object} search this symbolizer for property styles
+     * 
+     * Returns:
+     * {Object} propertyStyles hash
+     */
+    addPropertyStyles: function(propertyStyles, symbolizer) {
+        var property;
+        for (var key in symbolizer) {
+            property = symbolizer[key];
+            if (typeof property == "string" &&
+                    property.match(/\$\{\w+\}/)) {
+                propertyStyles[key] = true;
+            }
+        }
+        return propertyStyles;
+    },
+    
+    /**
+     * APIMethod: addRules
+     * Adds rules to this style.
+     * 
+     * Parameters:
+     * rules - {Array(<OpenLayers.Rule>)}
+     */
+    addRules: function(rules) {
+        this.rules = this.rules.concat(rules);
+        this.propertyStyles = this.findPropertyStyles();
+    },
+    
+    /**
+     * APIMethod: setDefaultStyle
+     * Sets the default style for this style object.
+     * 
+     * Parameters:
+     * style - {Object} Hash of style properties
+     */
+    setDefaultStyle: function(style) {
+        this.defaultStyle = style; 
+        this.propertyStyles = this.findPropertyStyles();
+    },
+        
+    /**
+     * Method: getSymbolizerPrefix
+     * Returns the correct symbolizer prefix according to the
+     * geometry type of the passed geometry
+     * 
+     * Parameters:
+     * geometry {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {String} key of the according symbolizer
+     */
+    getSymbolizerPrefix: function(geometry) {
+        var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
+        for (var i=0, len=prefixes.length; i<len; i++) {
+            if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
+                return prefixes[i];
+            }
+        }
+    },
+    
+    CLASS_NAME: "OpenLayers.Style"
+});
+
+
+/**
+ * Function: createLiteral
+ * converts a style value holding a combination of PropertyName and Literal
+ * into a Literal, taking the property values from the passed features.
+ * 
+ * Parameters:
+ * value - {String} value to parse. If this string contains a construct like
+ *         "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
+ *         will be replaced by the value of the "bar" attribute of the passed
+ *         feature.
+ * context - {Object} context to take attribute values from
+ * feature - {OpenLayers.Feature.Vector} The feature that will be passed
+ *     to <OpenLayers.String.format> for evaluating functions in the context.
+ * 
+ * Returns:
+ * {String} the parsed value. In the example of the value parameter above, the
+ * result would be "foo valueOfBar", assuming that the passed feature has an
+ * attribute named "bar" with the value "valueOfBar".
+ */
+OpenLayers.Style.createLiteral = function(value, context, feature) {
+    if (typeof value == "string" && value.indexOf("${") != -1) {
+        value = OpenLayers.String.format(value, context, [feature]);
+        value = (isNaN(value) || !value) ? value : parseFloat(value);
+    }
+    return value;
+};
+    
+/**
+ * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES
+ * {Array} prefixes of the sld symbolizers. These are the
+ * same as the main geometry types
+ */
+OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text'];
+/* ======================================================================
+    OpenLayers/Control/ModifyFeature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006 MetaCarta, Inc., published under the Clear BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt 
+ * for the full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control/DragFeature.js
+ * @requires OpenLayers/Control/SelectFeature.js
+ * @requires OpenLayers/Handler/Keyboard.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ModifyFeature
+ * Control to modify features.  When activated, a click renders the vertices
+ *     of a feature - these vertices can then be dragged.  By default, the
+ *     delete key will delete the vertex under the mouse.  New features are
+ *     added by dragging "virtual vertices" between vertices.  Create a new
+ *     control with the <OpenLayers.Control.ModifyFeature> constructor.
+ *
+ * Inherits From:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * APIProperty: geometryTypes
+     * {Array(String)} To restrict modification to a limited set of geometry
+     *     types, send a list of strings corresponding to the geometry class
+     *     names.
+     */
+    geometryTypes: null,
+
+    /**
+     * APIProperty: clickout
+     * {Boolean} Unselect features when clicking outside any feature.
+     *     Default is true.
+     */
+    clickout: true,
+
+    /**
+     * APIProperty: toggle
+     * {Boolean} Unselect a selected feature on click.
+     *      Default is true.
+     */
+    toggle: true,
+
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>}
+     */
+    layer: null,
+    
+    /**
+     * Property: feature
+     * {<OpenLayers.Feature.Vector>} Feature currently available for modification.
+     */
+    feature: null,
+    
+    /**
+     * Property: vertices
+     * {Array(<OpenLayers.Feature.Vector>)} Verticies currently available
+     *     for dragging.
+     */
+    vertices: null,
+    
+    /**
+     * Property: virtualVertices
+     * {Array(<OpenLayers.Feature.Vector>)} Virtual vertices in the middle
+     *     of each edge.
+     */
+    virtualVertices: null,
+
+    /**
+     * Property: selectControl
+     * {<OpenLayers.Control.SelectFeature>}
+     */
+    selectControl: null,
+    
+    /**
+     * Property: dragControl
+     * {<OpenLayers.Control.DragFeature>}
+     */
+    dragControl: null,
+    
+    /**
+     * Property: handlers
+     * {Object}
+     */
+    handlers: null,
+    
+    /**
+     * APIProperty: deleteCodes
+     * {Array(Integer)} Keycodes for deleting verticies.  Set to null to disable
+     *     vertex deltion by keypress.  If non-null, keypresses with codes
+     *     in this array will delete vertices under the mouse. Default
+     *     is 46 and 68, the 'delete' and lowercase 'd' keys.
+     */
+    deleteCodes: null,
+
+    /**
+     * APIProperty: virtualStyle
+     * {Object} A symbolizer to be used for virtual vertices.
+     */
+    virtualStyle: null,
+
+    /**
+     * APIProperty: mode
+     * {Integer} Bitfields specifying the modification mode. Defaults to
+     *      OpenLayers.Control.ModifyFeature.RESHAPE. To set the mode to a
+     *      combination of options, use the | operator. or example, to allow
+     *      the control to both resize and rotate features, use the following
+     *      syntax
+     * (code)
+     * control.mode = OpenLayers.Control.ModifyFeature.RESIZE |
+     *                OpenLayers.Control.ModifyFeature.ROTATE;
+     *  (end)
+     */
+    mode: null,
+
+    /**
+     * Property: radiusHandle
+     * {<OpenLayers.Feature.Vector>} A handle for rotating/resizing a feature.
+     */
+    radiusHandle: null,
+
+    /**
+     * Property: dragHandle
+     * {<OpenLayers.Feature.Vector>} A handle for dragging a feature.
+     */
+    dragHandle: null,
+
+    /**
+     * APIProperty: onModificationStart 
+     * {Function} *Deprecated*.  Register for "beforefeaturemodified" instead.
+     *     The "beforefeaturemodified" event is triggered on the layer before
+     *     any modification begins.
+     *
+     * Optional function to be called when a feature is selected
+     *     to be modified. The function should expect to be called with a
+     *     feature.  This could be used for example to allow to lock the
+     *     feature on server-side.
+     */
+    onModificationStart: function() {},
+
+    /**
+     * APIProperty: onModification
+     * {Function} *Deprecated*.  Register for "featuremodified" instead.
+     *     The "featuremodified" event is triggered on the layer with each
+     *     feature modification.
+     *
+     * Optional function to be called when a feature has been
+     *     modified.  The function should expect to be called with a feature.
+     */
+    onModification: function() {},
+
+    /**
+     * APIProperty: onModificationEnd
+     * {Function} *Deprecated*.  Register for "afterfeaturemodified" instead.
+     *     The "afterfeaturemodified" event is triggered on the layer after
+     *     a feature has been modified.
+     *
+     * Optional function to be called when a feature is finished 
+     *     being modified.  The function should expect to be called with a
+     *     feature.
+     */
+    onModificationEnd: function() {},
+
+    /**
+     * Constructor: OpenLayers.Control.ModifyFeature
+     * Create a new modify feature control.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.Vector>} Layer that contains features that
+     *     will be modified.
+     * options - {Object} Optional object whose properties will be set on the
+     *     control.
+     */
+    initialize: function(layer, options) {
+        this.layer = layer;
+        this.vertices = [];
+        this.virtualVertices = [];
+        this.virtualStyle = OpenLayers.Util.extend({},
+            this.layer.style || this.layer.styleMap.createSymbolizer());
+        this.virtualStyle.fillOpacity = 0.3;
+        this.virtualStyle.strokeOpacity = 0.3;
+        this.deleteCodes = [46, 68];
+        this.mode = OpenLayers.Control.ModifyFeature.RESHAPE;
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        if(!(this.deleteCodes instanceof Array)) {
+            this.deleteCodes = [this.deleteCodes];
+        }
+        var control = this;
+
+        // configure the select control
+        var selectOptions = {
+            geometryTypes: this.geometryTypes,
+            clickout: this.clickout,
+            toggle: this.toggle
+        };
+        this.selectControl = new OpenLayers.Control.SelectFeature(
+            layer, selectOptions
+        );
+        this.layer.events.on({
+            "beforefeatureselected": this.beforeSelectFeature,
+            "featureselected": this.selectFeature,
+            "featureunselected": this.unselectFeature,
+            scope: this
+        });
+
+        // configure the drag control
+        var dragOptions = {
+            geometryTypes: ["OpenLayers.Geometry.Point"],
+            snappingOptions: this.snappingOptions,
+            onStart: function(feature, pixel) {
+                control.dragStart.apply(control, [feature, pixel]);
+            },
+            onDrag: function(feature) {
+                control.dragVertex.apply(control, [feature]);
+            },
+            onComplete: function(feature) {
+                control.dragComplete.apply(control, [feature]);
+            }
+        };
+        this.dragControl = new OpenLayers.Control.DragFeature(
+            layer, dragOptions
+        );
+
+        // configure the keyboard handler
+        var keyboardOptions = {
+            keydown: this.handleKeypress
+        };
+        this.handlers = {
+            keyboard: new OpenLayers.Handler.Keyboard(this, keyboardOptions)
+        };
+    },
+
+    /**
+     * APIMethod: destroy
+     * Take care of things that are not handled in superclass.
+     */
+    destroy: function() {
+        this.layer.events.un({
+            "beforefeatureselected": this.beforeSelectFeature,
+            "featureselected": this.selectFeature,
+            "featureunselected": this.unselectFeature,
+            scope: this
+        });
+        this.layer = null;
+        this.selectControl.destroy();
+        this.dragControl.destroy();
+        OpenLayers.Control.prototype.destroy.apply(this, []);
+    },
+
+    /**
+     * APIMethod: activate
+     * Activate the control.
+     * 
+     * Returns:
+     * {Boolean} Successfully activated the control.
+     */
+    activate: function() {
+        return (this.selectControl.activate() &&
+                this.handlers.keyboard.activate() &&
+                OpenLayers.Control.prototype.activate.apply(this, arguments));
+    },
+
+    /**
+     * APIMethod: deactivate
+     * Deactivate the control.
+     *
+     * Returns: 
+     * {Boolean} Successfully deactivated the control.
+     */
+    deactivate: function() {
+        var deactivated = false;
+        // the return from the controls is unimportant in this case
+        if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+            this.layer.removeFeatures(this.vertices, {silent: true});
+            this.layer.removeFeatures(this.virtualVertices, {silent: true});
+            this.vertices = [];
+            this.dragControl.deactivate();
+            if(this.feature && this.feature.geometry) {
+                this.selectControl.unselect.apply(this.selectControl,
+                                                  [this.feature]);
+            }
+            this.selectControl.deactivate();
+            this.handlers.keyboard.deactivate();
+            deactivated = true;
+        }
+        return deactivated;
+    },
+    
+    /**
+     * Method: beforeSelectFeature
+     * Called before a feature is selected.
+     *
+     * Parameters:
+     * object - {Object} Object with a feature property referencing the
+     *     selected feature.
+     */
+    beforeSelectFeature: function(object) {
+        return this.layer.events.triggerEvent(
+            "beforefeaturemodified", {feature: object.feature}
+        );
+    },
+
+    /**
+     * Method: selectFeature
+     * Called when the select feature control selects a feature.
+     *
+     * Parameters:
+     * object - {Object} Object with a feature property referencing the
+     *     selected feature.
+     */
+    selectFeature: function(object) {
+        this.feature = object.feature;
+        this.resetVertices();
+        this.dragControl.activate();
+        this.onModificationStart(this.feature);
+    },
+
+    /**
+     * Method: unselectFeature
+     * Called when the select feature control unselects a feature.
+     *
+     * Parameters:
+     * object - {Object} Object with a feature property referencing the
+     *     unselected feature.
+     */
+    unselectFeature: function(object) {
+        this.layer.removeFeatures(this.vertices, {silent: true});
+        this.vertices = [];
+        this.layer.destroyFeatures(this.virtualVertices, {silent: true});
+        this.virtualVertices = [];
+        if(this.dragHandle) {
+            this.layer.destroyFeatures([this.dragHandle], {silent: true});
+            delete this.dragHandle;
+        }
+        if(this.radiusHandle) {
+            this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+            delete this.radiusHandle;
+        }
+        this.feature = null;
+        this.dragControl.deactivate();
+        this.onModificationEnd(object.feature);
+        this.layer.events.triggerEvent("afterfeaturemodified", 
+                                       {feature: object.feature});
+    },
+
+    /**
+     * Method: dragStart
+     * Called by the drag feature control with before a feature is dragged.
+     *     This method is used to differentiate between points and vertices
+     *     of higher order geometries.  This respects the <geometryTypes>
+     *     property and forces a select of points when the drag control is
+     *     already active (and stops events from propagating to the select
+     *     control).
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The point or vertex about to be
+     *     dragged.
+     * pixel - {<OpenLayers.Pixel>} Pixel location of the mouse event.
+     */
+    dragStart: function(feature, pixel) {
+        // only change behavior if the feature is not in the vertices array
+        if(feature != this.feature && !feature.geometry.parent &&
+           feature != this.dragHandle && feature != this.radiusHandle) {
+            if(this.feature) {
+                // unselect the currently selected feature
+                this.selectControl.clickFeature.apply(this.selectControl,
+                                                      [this.feature]);
+            }
+            // check any constraints on the geometry type
+            if(this.geometryTypes == null ||
+               OpenLayers.Util.indexOf(this.geometryTypes,
+                                       feature.geometry.CLASS_NAME) != -1) {
+                // select the point
+                this.selectControl.clickFeature.apply(this.selectControl,
+                                                      [feature]);
+                /**
+                 * TBD: These lines improve workflow by letting the user
+                 *     immediately start dragging after the mouse down.
+                 *     However, it is very ugly to be messing with controls
+                 *     and their handlers in this way.  I'd like a better
+                 *     solution if the workflow change is necessary.
+                 */
+                // prepare the point for dragging
+                this.dragControl.overFeature.apply(this.dragControl,
+                                                   [feature]);
+                this.dragControl.lastPixel = pixel;
+                this.dragControl.handlers.drag.started = true;
+                this.dragControl.handlers.drag.start = pixel;
+                this.dragControl.handlers.drag.last = pixel;
+            }
+        }
+    },
+    
+    /**
+     * Method: dragVertex
+     * Called by the drag feature control with each drag move of a vertex.
+     *
+     * Parameters:
+     * vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+     */
+    dragVertex: function(vertex) {
+        /**
+         * Five cases:
+         * 1) dragging a simple point
+         * 2) dragging a virtual vertex
+         * 3) dragging a drag handle
+         * 4) dragging a radius handle
+         * 5) dragging a real vertex
+         */
+        if(this.feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+            // dragging a simple point
+            if(this.feature != vertex) {
+                this.feature = vertex;
+            }
+        } else {
+            if(vertex._index) {
+                // dragging a virtual vertex
+                vertex.geometry.parent.addComponent(vertex.geometry,
+                                                    vertex._index);
+                // move from virtual to real vertex
+                delete vertex._index;
+                OpenLayers.Util.removeItem(this.virtualVertices, vertex);
+                this.vertices.push(vertex);
+            } else if(vertex == this.dragHandle) {
+                // dragging a drag handle
+                this.layer.removeFeatures(this.vertices, {silent: true});
+                this.vertices = [];
+                if(this.radiusHandle) {
+                    this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+                    this.radiusHandle = null;
+                }
+            }
+            // dragging a radius handle - no special treatment
+            // dragging a real vertex - no special treatment
+            if(this.virtualVertices.length > 0) {
+                this.layer.destroyFeatures(this.virtualVertices, {silent: true});
+                this.virtualVertices = [];
+            }
+            this.layer.drawFeature(this.feature, this.selectControl.renderIntent);
+        }
+        // keep the vertex on top so it gets the mouseout after dragging
+        // this should be removed in favor of an option to draw under or
+        // maintain node z-index
+        this.layer.drawFeature(vertex);
+    },
+    
+    /**
+     * Method: dragComplete
+     * Called by the drag feature control when the feature dragging is complete.
+     *
+     * Parameters:
+     * vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+     */
+    dragComplete: function(vertex) {
+        this.resetVertices();
+        this.onModification(this.feature);
+        this.layer.events.triggerEvent("featuremodified", 
+                                       {feature: this.feature});
+    },
+    
+    /**
+     * Method: resetVertices
+     */
+    resetVertices: function() {
+        // if coming from a drag complete we're about to destroy the vertex
+        // that was just dragged. For that reason, the drag feature control
+        // will never detect a mouse-out on that vertex, meaning that the drag
+        // handler won't be deactivated. This can cause errors because the drag
+        // feature control still has a feature to drag but that feature is
+        // destroyed. To prevent this, we call outFeature on the drag feature
+        // control if the control actually has a feature to drag.
+        if(this.dragControl.feature) {
+            this.dragControl.outFeature(this.dragControl.feature);
+        }
+        if(this.vertices.length > 0) {
+            this.layer.removeFeatures(this.vertices, {silent: true});
+            this.vertices = [];
+        }
+        if(this.virtualVertices.length > 0) {
+            this.layer.removeFeatures(this.virtualVertices, {silent: true});
+            this.virtualVertices = [];
+        }
+        if(this.dragHandle) {
+            this.layer.destroyFeatures([this.dragHandle], {silent: true});
+            this.dragHandle = null;
+        }
+        if(this.radiusHandle) {
+            this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+            this.radiusHandle = null;
+        }
+        if(this.feature &&
+           this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
+            if((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
+                this.collectDragHandle();
+            }
+            if((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
+                             OpenLayers.Control.ModifyFeature.RESIZE))) {
+                this.collectRadiusHandle();
+            }
+            if((this.mode & OpenLayers.Control.ModifyFeature.RESHAPE)) {
+                this.collectVertices();
+            }
+        }
+    },
+    
+    /**
+     * Method: handleKeypress
+     * Called by the feature handler on keypress.  This is used to delete
+     *     vertices. If the <deleteCode> property is set, vertices will
+     *     be deleted when a feature is selected for modification and
+     *     the mouse is over a vertex.
+     *
+     * Parameters:
+     * {Integer} Key code corresponding to the keypress event.
+     */
+    handleKeypress: function(evt) {
+        var code = evt.keyCode;
+        
+        // check for delete key
+        if(this.feature &&
+           OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) {
+            var vertex = this.dragControl.feature;
+            if(vertex &&
+               OpenLayers.Util.indexOf(this.vertices, vertex) != -1 &&
+               !this.dragControl.handlers.drag.dragging &&
+               vertex.geometry.parent) {
+                // remove the vertex
+                vertex.geometry.parent.removeComponent(vertex.geometry);
+                this.layer.drawFeature(this.feature,
+                                       this.selectControl.renderIntent);
+                this.resetVertices();
+                this.onModification(this.feature);
+                this.layer.events.triggerEvent("featuremodified", 
+                                               {feature: this.feature});
+            }
+        }
+    },
+
+    /**
+     * Method: collectVertices
+     * Collect the vertices from the modifiable feature's geometry and push
+     *     them on to the control's vertices array.
+     */
+    collectVertices: function() {
+        this.vertices = [];
+        this.virtualVertices = [];        
+        var control = this;
+        function collectComponentVertices(geometry) {
+            var i, vertex, component, len;
+            if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+                vertex = new OpenLayers.Feature.Vector(geometry);
+                control.vertices.push(vertex);
+            } else {
+                var numVert = geometry.components.length;
+                if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+                    numVert -= 1;
+                }
+                for(i=0; i<numVert; ++i) {
+                    component = geometry.components[i];
+                    if(component.CLASS_NAME == "OpenLayers.Geometry.Point") {
+                        vertex = new OpenLayers.Feature.Vector(component);
+                        control.vertices.push(vertex);
+                    } else {
+                        collectComponentVertices(component);
+                    }
+                }
+                
+                // add virtual vertices in the middle of each edge
+                if(geometry.CLASS_NAME != "OpenLayers.Geometry.MultiPoint") {
+                    for(i=0, len=geometry.components.length; i<len-1; ++i) {
+                        var prevVertex = geometry.components[i];
+                        var nextVertex = geometry.components[i + 1];
+                        if(prevVertex.CLASS_NAME == "OpenLayers.Geometry.Point" &&
+                           nextVertex.CLASS_NAME == "OpenLayers.Geometry.Point") {
+                            var x = (prevVertex.x + nextVertex.x) / 2;
+                            var y = (prevVertex.y + nextVertex.y) / 2;
+                            var point = new OpenLayers.Feature.Vector(
+                                new OpenLayers.Geometry.Point(x, y),
+                                null, control.virtualStyle
+                            );
+                            // set the virtual parent and intended index
+                            point.geometry.parent = geometry;
+                            point._index = i + 1;
+                            control.virtualVertices.push(point);
+                        }
+                    }
+                }
+            }
+        }
+        collectComponentVertices.call(this, this.feature.geometry);
+        this.layer.addFeatures(this.virtualVertices, {silent: true});
+        this.layer.addFeatures(this.vertices, {silent: true});
+    },
+
+    /**
+     * Method: collectDragHandle
+     * Collect the drag handle for the selected geometry.
+     */
+    collectDragHandle: function() {
+        var geometry = this.feature.geometry;
+        var center = geometry.getBounds().getCenterLonLat();
+        var originGeometry = new OpenLayers.Geometry.Point(
+            center.lon, center.lat
+        );
+        var origin = new OpenLayers.Feature.Vector(originGeometry);
+        originGeometry.move = function(x, y) {
+            OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+            geometry.move(x, y);
+        };
+        this.dragHandle = origin;
+        this.layer.addFeatures([this.dragHandle], {silent: true});
+    },
+
+    /**
+     * Method: collectRadiusHandle
+     * Collect the radius handle for the selected geometry.
+     */
+    collectRadiusHandle: function() {
+        var geometry = this.feature.geometry;
+        var bounds = geometry.getBounds();
+        var center = bounds.getCenterLonLat();
+        var originGeometry = new OpenLayers.Geometry.Point(
+            center.lon, center.lat
+        );
+        var radiusGeometry = new OpenLayers.Geometry.Point(
+            bounds.right, bounds.bottom
+        );
+        var radius = new OpenLayers.Feature.Vector(radiusGeometry);
+        var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
+        var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
+        radiusGeometry.move = function(x, y) {
+            OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+            var dx1 = this.x - originGeometry.x;
+            var dy1 = this.y - originGeometry.y;
+            var dx0 = dx1 - x;
+            var dy0 = dy1 - y;
+            if(rotate) {
+                var a0 = Math.atan2(dy0, dx0);
+                var a1 = Math.atan2(dy1, dx1);
+                var angle = a1 - a0;
+                angle *= 180 / Math.PI;
+                geometry.rotate(angle, originGeometry);
+            }
+            if(resize) {
+                var l0 = Math.sqrt((dx0 * dx0) + (dy0 * dy0));
+                var l1 = Math.sqrt((dx1 * dx1) + (dy1 * dy1));
+                geometry.resize(l1 / l0, originGeometry);
+            }
+        };
+        this.radiusHandle = radius;
+        this.layer.addFeatures([this.radiusHandle], {silent: true});
+    },
+
+    /**
+     * Method: setMap
+     * Set the map property for the control and all handlers.
+     *
+     * Parameters:
+     * map - {<OpenLayers.Map>} The control's map.
+     */
+    setMap: function(map) {
+        this.selectControl.setMap(map);
+        this.dragControl.setMap(map);
+        OpenLayers.Control.prototype.setMap.apply(this, arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Control.ModifyFeature"
+});
+
+/**
+ * Constant: RESHAPE
+ * {Integer} Constant used to make the control work in reshape mode
+ */
+OpenLayers.Control.ModifyFeature.RESHAPE = 1;
+/**
+ * Constant: RESIZE
+ * {Integer} Constant used to make the control work in resize mode
+ */
+OpenLayers.Control.ModifyFeature.RESIZE = 2;
+/**
+ * Constant: ROTATE
+ * {Integer} Constant used to make the control work in rotate mode
+ */
+OpenLayers.Control.ModifyFeature.ROTATE = 4;
+/**
+ * Constant: DRAG
+ * {Integer} Constant used to make the control work in drag mode
+ */
+OpenLayers.Control.ModifyFeature.DRAG = 8;
+/* ======================================================================
     OpenLayers/Control/Navigation.js
    ====================================================================== */
 
@@ -14711,6 +34938,12 @@
      */
     dragPan: null,
 
+    /**
+     * APIProprety: dragPanOptions
+     * {Object} Options passed to the DragPan control.
+     */
+    dragPanOptions: null,
+
     /** 
      * Property: zoomBox
      * {<OpenLayers.Control.ZoomBox>}
@@ -14724,6 +34957,12 @@
     zoomWheelEnabled: true, 
 
     /**
+     * APIProperty: handleRightClicks
+     * {Boolean} Whether or not to handle right clicks. Default is false.
+     */
+    handleRightClicks: true,
+    
+    /**
      * Constructor: OpenLayers.Control.Navigation
      * Create a new navigation control
      * 
@@ -14785,13 +35024,25 @@
      * Method: draw
      */
     draw: function() {
-        this.handlers.click = new OpenLayers.Handler.Click(this, 
-                                        { 'dblclick': this.defaultDblClick },
-                                        {
-                                          'double': true, 
-                                          'stopDouble': true
-                                        });
-        this.dragPan = new OpenLayers.Control.DragPan({map: this.map});
+        // disable right mouse context menu for support of right click events
+        if (this.handleRightClicks) {
+            this.map.div.oncontextmenu = function () { return false;};
+        }
+
+        var clickCallbacks = { 
+            'dblclick': this.defaultDblClick, 
+            'dblrightclick': this.defaultDblRightClick 
+        };
+        var clickOptions = {
+            'double': true, 
+            'stopDouble': true
+        };
+        this.handlers.click = new OpenLayers.Handler.Click(
+            this, clickCallbacks, clickOptions
+        );
+        this.dragPan = new OpenLayers.Control.DragPan(
+            OpenLayers.Util.extend({map: this.map}, this.dragPanOptions)
+        );
         this.zoomBox = new OpenLayers.Control.ZoomBox(
                     {map: this.map, keyMask: OpenLayers.Handler.MOD_SHIFT});
         this.dragPan.draw();
@@ -14814,6 +35065,17 @@
     },
 
     /**
+     * Method: defaultRightDblClick 
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultDblRightClick: function (evt) {
+        var newCenter = this.map.getLonLatFromViewPortPx( evt.xy ); 
+        this.map.setCenter(newCenter, this.map.zoom - 1);
+    },
+    
+    /**
      * Method: wheelChange  
      *
      * Parameters:
@@ -14881,6 +35143,546 @@
     CLASS_NAME: "OpenLayers.Control.Navigation"
 });
 /* ======================================================================
+    OpenLayers/Filter.js
+   ====================================================================== */
+
+/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Filter
+ * This class represents an OGC Filter.
+ */
+OpenLayers.Filter = OpenLayers.Class({
+    
+    /** 
+     * Constructor: OpenLayers.Filter
+     * This is an abstract class.  Create an instance of a filter subclass.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     * 
+     * Returns:
+     * {<OpenLayers.Filter>}
+     */
+    initialize: function(options) {
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /** 
+     * APIMethod: destroy
+     * Remove reference to anything added.
+     */
+    destroy: function() {
+    },
+
+    /**
+     * APIMethod: evaluate
+     * Evaluates this filter in a specific context.  Should be implemented by
+     *     subclasses.
+     * 
+     * Parameters:
+     * context - {Object} Context to use in evaluating the filter.
+     * 
+     * Returns:
+     * {Boolean} The filter applies.
+     */
+    evaluate: function(context) {
+        return true;
+    },
+    
+    CLASS_NAME: "OpenLayers.Filter"
+});
+/* ======================================================================
+    OpenLayers/Geometry.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+ 
+/**
+ * @requires OpenLayers/Format/WKT.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry
+ * A Geometry is a description of a geographic object.  Create an instance of
+ * this class with the <OpenLayers.Geometry> constructor.  This is a base class,
+ * typical geometry types are described by subclasses of this class.
+ */
+OpenLayers.Geometry = OpenLayers.Class({
+
+    /**
+     * Property: id
+     * {String} A unique identifier for this geometry.
+     */
+    id: null,
+
+    /**
+     * Property: parent
+     * {<OpenLayers.Geometry>}This is set when a Geometry is added as component
+     * of another geometry
+     */
+    parent: null,
+
+    /**
+     * Property: bounds 
+     * {<OpenLayers.Bounds>} The bounds of this geometry
+     */
+    bounds: null,
+
+    /**
+     * Constructor: OpenLayers.Geometry
+     * Creates a geometry object.  
+     */
+    initialize: function() {
+        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_");
+    },
+    
+    /**
+     * Method: destroy
+     * Destroy this geometry.
+     */
+    destroy: function() {
+        this.id = null;
+        this.bounds = null;
+    },
+    
+    /**
+     * APIMethod: clone
+     * Create a clone of this geometry.  Does not set any non-standard
+     *     properties of the cloned geometry.
+     * 
+     * Returns:
+     * {<OpenLayers.Geometry>} An exact clone of this geometry.
+     */
+    clone: function() {
+        return new OpenLayers.Geometry();
+    },
+    
+    /**
+     * Set the bounds for this Geometry.
+     * 
+     * Parameters:
+     * object - {<OpenLayers.Bounds>} 
+     */
+    setBounds: function(bounds) {
+        if (bounds) {
+            this.bounds = bounds.clone();
+        }
+    },
+    
+    /**
+     * Method: clearBounds
+     * Nullify this components bounds and that of its parent as well.
+     */
+    clearBounds: function() {
+        this.bounds = null;
+        if (this.parent) {
+            this.parent.clearBounds();
+        }    
+    },
+    
+    /**
+     * Method: extendBounds
+     * Extend the existing bounds to include the new bounds. 
+     * If geometry's bounds is not yet set, then set a new Bounds.
+     * 
+     * Parameters:
+     * newBounds - {<OpenLayers.Bounds>} 
+     */
+    extendBounds: function(newBounds){
+        var bounds = this.getBounds();
+        if (!bounds) {
+            this.setBounds(newBounds);
+        } else {
+            this.bounds.extend(newBounds);
+        }
+    },
+    
+    /**
+     * APIMethod: getBounds
+     * Get the bounds for this Geometry. If bounds is not set, it 
+     * is calculated again, this makes queries faster.
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>}
+     */
+    getBounds: function() {
+        if (this.bounds == null) {
+            this.calculateBounds();
+        }
+        return this.bounds;
+    },
+    
+    /** 
+     * APIMethod: calculateBounds
+     * Recalculate the bounds for the geometry. 
+     */
+    calculateBounds: function() {
+        //
+        // This should be overridden by subclasses.
+        //
+    },
+    
+    /**
+     * Method: atPoint
+     * Note - This is only an approximation based on the bounds of the 
+     * geometry.
+     * 
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>} 
+     * toleranceLon - {float} Optional tolerance in Geometric Coords
+     * toleranceLat - {float} Optional tolerance in Geographic Coords
+     * 
+     * Returns:
+     * {Boolean} Whether or not the geometry is at the specified location
+     */
+    atPoint: function(lonlat, toleranceLon, toleranceLat) {
+        var atPoint = false;
+        var bounds = this.getBounds();
+        if ((bounds != null) && (lonlat != null)) {
+
+            var dX = (toleranceLon != null) ? toleranceLon : 0;
+            var dY = (toleranceLat != null) ? toleranceLat : 0;
+    
+            var toleranceBounds = 
+                new OpenLayers.Bounds(this.bounds.left - dX,
+                                      this.bounds.bottom - dY,
+                                      this.bounds.right + dX,
+                                      this.bounds.top + dY);
+
+            atPoint = toleranceBounds.containsLonLat(lonlat);
+        }
+        return atPoint;
+    },
+    
+    /**
+     * Method: getLength
+     * Calculate the length of this geometry. This method is defined in
+     * subclasses.
+     * 
+     * Returns:
+     * {Float} The length of the collection by summing its parts
+     */
+    getLength: function() {
+        //to be overridden by geometries that actually have a length
+        //
+        return 0.0;
+    },
+
+    /**
+     * Method: getArea
+     * Calculate the area of this geometry. This method is defined in subclasses.
+     * 
+     * Returns:
+     * {Float} The area of the collection by summing its parts
+     */
+    getArea: function() {
+        //to be overridden by geometries that actually have an area
+        //
+        return 0.0;
+    },
+
+    /**
+     * Method: toString
+     * Returns the Well-Known Text representation of a geometry
+     *
+     * Returns:
+     * {String} Well-Known Text
+     */
+    toString: function() {
+        return OpenLayers.Format.WKT.prototype.write(
+            new OpenLayers.Feature.Vector(this)
+        );
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry"
+});
+
+    
+/**
+ * Method: OpenLayers.Geometry.segmentsIntersect
+ * Determine whether two line segments intersect.  Optionally calculates
+ *     and returns the intersection point.  This function is optimized for
+ *     cases where seg1.x2 >= seg2.x1 || seg2.x2 >= seg1.x1.  In those
+ *     obvious cases where there is no intersection, the function should
+ *     not be called.
+ *
+ * Parameters:
+ * seg1 - {Object} Object representing a segment with properties x1, y1, x2,
+ *     and y2.  The start point is represented by x1 and y1.  The end point
+ *     is represented by x2 and y2.  Start and end are ordered so that x1 < x2.
+ * seg2 - {Object} Object representing a segment with properties x1, y1, x2,
+ *     and y2.  The start point is represented by x1 and y1.  The end point
+ *     is represented by x2 and y2.  Start and end are ordered so that x1 < x2.
+ * point - {Boolean} Return the intersection point.  If false, the actual
+ *     intersection point will not be calculated.  If true and the segments
+ *     intersect, the intersection point will be returned.  If true and
+ *     the segments do not intersect, false will be returned.  If true and
+ *     the segments are coincident, true will be returned.
+ *
+ * Returns:
+ * {Boolean | <OpenLayers.Geometry.Point>}  The two segments intersect.
+ *     If the point argument is true, the return will be the intersection
+ *     point or false if none exists.  If point is true and the segments
+ *     are coincident, return will be true (and the instersection is equal
+ *     to the shorter segment).
+ */
+OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, point) {
+    var intersection = false;
+    var x11_21 = seg1.x1 - seg2.x1;
+    var y11_21 = seg1.y1 - seg2.y1;
+    var x12_11 = seg1.x2 - seg1.x1;
+    var y12_11 = seg1.y2 - seg1.y1;
+    var y22_21 = seg2.y2 - seg2.y1;
+    var x22_21 = seg2.x2 - seg2.x1;
+    var d = (y22_21 * x12_11) - (x22_21 * y12_11);
+    var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
+    var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
+    if(d == 0) {
+        // parallel
+        if(n1 == 0 && n2 == 0) {
+            // coincident
+            intersection = true;
+        }
+    } else {
+        var along1 = n1 / d;
+        var along2 = n2 / d;
+        if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
+            // intersect
+            if(!point) {
+                intersection = true;
+            } else {
+                // calculate the intersection point
+                var x = seg1.x1 + (along1 * x12_11);
+                var y = seg1.y1 + (along1 * y12_11);
+                intersection = new OpenLayers.Geometry.Point(x, y);
+            }
+        }
+    }
+    return intersection;
+};
+/* ======================================================================
+    OpenLayers/Layer/KaMap.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.KaMap
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.KaMap = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+    /** 
+     * APIProperty: isBaseLayer
+     * {Boolean} KaMap Layer is always a base layer 
+     */    
+    isBaseLayer: true,
+
+    /**
+     * APIProperty: units
+     * {?}
+     */    
+    units: null,
+
+    /**
+     * APIProperty: resolution
+     * {Float}
+     */
+    resolution: OpenLayers.DOTS_PER_INCH,
+    
+    /**
+     * Constant: DEFAULT_PARAMS
+     * {Object} parameters set by default. The default parameters set 
+     * the format via the 'i' parameter to 'jpeg'.    
+     */
+    DEFAULT_PARAMS: {
+        i: 'jpeg',
+        map: ''
+    },
+        
+    /**
+     * Constructor: OpenLayers.Layer.KaMap
+     * 
+     * Parameters:
+     * name - {String}
+     * url - {String}
+     * params - {Object} Parameters to be sent to the HTTP server in the
+     *    query string for the tile. The format can be set via the 'i'
+     *    parameter (defaults to jpg) , and the map should be set via 
+     *    the 'map' parameter. It has been reported that ka-Map may behave
+     *    inconsistently if your format parameter does not match the format
+     *    parameter configured in your config.php. (See ticket #327 for more
+     *    information.)
+     * options - {Object} Additional options for the layer. Any of the 
+     *     APIProperties listed on this layer, and any layer types it
+     *     extends, can be overridden through the options parameter. 
+     */
+    initialize: function(name, url, params, options) {
+        var newArguments = [];
+        newArguments.push(name, url, params, options);
+        OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+        this.params = OpenLayers.Util.applyDefaults(
+            this.params, this.DEFAULT_PARAMS
+        );
+    },
+
+    /**
+     * Method: getURL
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     * 
+     * Returns:
+     * {String} A string with the layer's url and parameters and also the 
+     *          passed-in bounds and appropriate tile size specified as 
+     *          parameters
+     */
+    getURL: function (bounds) {
+        bounds = this.adjustBounds(bounds);
+        var mapRes = this.map.getResolution();
+        var scale = Math.round((this.map.getScale() * 10000)) / 10000;
+        var pX = Math.round(bounds.left / mapRes);
+        var pY = -Math.round(bounds.top / mapRes);
+        return this.getFullRequestString(
+                      { t: pY, 
+                        l: pX,
+                        s: scale
+                      });
+    },
+
+    /**
+     * Method: addTile
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * position - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {<OpenLayers.Tile.Image>}
+     */    
+    addTile:function(bounds,position) {
+        var url = this.getURL(bounds);
+        return new OpenLayers.Tile.Image(this, position, bounds, 
+                                             url, this.tileSize);
+    },
+
+    /** 
+     * Method: calculateGridLayout
+     * ka-Map uses the center point of the map as an origin for 
+     * its tiles. Override calculateGridLayout to center tiles 
+     * correctly for this case.
+     *
+     * Parameters:
+     * bounds - {<OpenLayers.Bound>}
+     * extent - {<OpenLayers.Bounds>}
+     * resolution - {Number}
+     *
+     * Returns:
+     * Object containing properties tilelon, tilelat, tileoffsetlat,
+     * tileoffsetlat, tileoffsetx, tileoffsety
+     */
+    calculateGridLayout: function(bounds, extent, resolution) {
+        var tilelon = resolution*this.tileSize.w;
+        var tilelat = resolution*this.tileSize.h;
+        
+        var offsetlon = bounds.left;
+        var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
+        var tilecolremain = offsetlon/tilelon - tilecol;
+        var tileoffsetx = -tilecolremain * this.tileSize.w;
+        var tileoffsetlon = tilecol * tilelon;
+        
+        var offsetlat = bounds.top;  
+        var tilerow = Math.ceil(offsetlat/tilelat) + this.buffer;
+        var tilerowremain = tilerow - offsetlat/tilelat;
+        var tileoffsety = -(tilerowremain+1) * this.tileSize.h;
+        var tileoffsetlat = tilerow * tilelat;
+        
+        return { 
+          tilelon: tilelon, tilelat: tilelat,
+          tileoffsetlon: tileoffsetlon, tileoffsetlat: tileoffsetlat,
+          tileoffsetx: tileoffsetx, tileoffsety: tileoffsety
+        };
+    },    
+
+    /**
+     * APIMethod: clone
+     * 
+     * Parameters: 
+     * obj - {Object}
+     * 
+     * Returns:
+     * {<OpenLayers.Layer.Kamap>} An exact clone of this OpenLayers.Layer.KaMap
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.KaMap(this.name,
+                                            this.url,
+                                            this.params,
+                                            this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+        if (this.tileSize != null) {
+            obj.tileSize = this.tileSize.clone();
+        }
+        
+        // we do not want to copy reference to grid, so we make a new array
+        obj.grid = [];
+
+        return obj;
+    },    
+    
+    /**
+     * APIMethod: getTileBounds
+     * Returns The tile bounds for a layer given a pixel location.
+     *
+     * Parameters:
+     * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
+     */
+    getTileBounds: function(viewPortPx) {
+        var resolution = this.getResolution();
+        var tileMapWidth = resolution * this.tileSize.w;
+        var tileMapHeight = resolution * this.tileSize.h;
+        var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
+        var tileLeft = tileMapWidth * Math.floor(mapPoint.lon / tileMapWidth);
+        var tileBottom = tileMapHeight * Math.floor(mapPoint.lat / tileMapHeight);
+        return new OpenLayers.Bounds(tileLeft, tileBottom,
+                                     tileLeft + tileMapWidth,
+                                     tileBottom + tileMapHeight);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.KaMap"
+});
+/* ======================================================================
     OpenLayers/Layer/MapGuide.js
    ====================================================================== */
 
@@ -14889,7 +35691,7 @@
  * full text of the license. */
 
 /**
- * @requires OpenLayers/Ajax.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
  * @requires OpenLayers/Layer/Grid.js
  */
 
@@ -14914,7 +35716,7 @@
      * {Boolean} use tile server or request single tile image. Note that using
      *    singleTile *and* isBaseLayer false is *not recommend*: it uses synchronous
      *    XMLHttpRequests to load tiles, and this will *lock up users browsers*
-     *    during requests.
+     *    during requests if the server fails to respond.
      **/
     singleTile: false,
     
@@ -14949,18 +35751,29 @@
      * Constructor: OpenLayers.Layer.MapGuide
      * Create a new Mapguide layer, either tiled or untiled.  
      *
-     * For tiled layers, the 'groupName' and 'mapDefnition' options 
-     * must be specified as options.
+     * For tiled layers, the 'groupName' and 'mapDefinition' values 
+     * must be specified as parameters in the constructor.
      *
-     * For untiled layers, specify either combination of 'mapName' and
-     * 'session', or 'mapDefinition' and 'locale'.
+     * For untiled base layers, specify either combination of 'mapName' and
+     * 'session', or 'mapDefinition' and 'locale'.  
      *
+     * For untiled overlay layers (singleTile=true and isBaseLayer=false), 
+     * mapName and session are required parameters for the Layer constructor.  
+     * Also NOTE: untiled overlay layers issues a synchronous AJAX request 
+     * before the image request can be issued so the users browser may lock
+     * up if the MG Web tier does not respond in a timely fashion.
+     *
+     * NOTE: MapGuide OS uses a DPI value and degrees to meters conversion 
+     * factor that are different than the defaults used in OpenLayers, 
+     * so these must be adjusted accordingly in your application.  
+     * See the MapGuide example for how to set these values for MGOS.
+     *
      * Parameters:
      * name - {String} Name of the layer displayed in the interface
      * url - {String} Location of the MapGuide mapagent executable
      *            (e.g. http://localhost:8008/mapguide/mapagent/mapagent.fcgi)
      * params - {Object} hashtable of additional parameters to use. Some
-     *     parameters may require additional code on the serer. The ones that
+     *     parameters may require additional code on the server. The ones that
      *     you may want to use are: 
      *   - mapDefinition - {String} The MapGuide resource definition
      *            (e.g. Library://Samples/Gmap/Maps/gmapTiled.MapDefinition)
@@ -15002,6 +35815,7 @@
                            this.params,
                            this.SINGLE_TILE_PARAMS
                            );
+                           
         } else {
             //initialize for tiled layers
             OpenLayers.Util.applyDefaults(
@@ -15082,18 +35896,15 @@
             
             //but we first need to call GETVISIBLEMAPEXTENT to set the extent
             var getVisParams = {};
+            getVisParams = OpenLayers.Util.extend(getVisParams, params);
             getVisParams.operation = "GETVISIBLEMAPEXTENT";
             getVisParams.version = "1.0.0";
             getVisParams.session = this.params.session;
             getVisParams.mapName = this.params.mapName;
             getVisParams.format = 'text/xml';
-            getVisParams = OpenLayers.Util.extend(getVisParams, params);
-                
-            new OpenLayers.Ajax.Request(this.url, 
-                  { parameters: getVisParams,
-                    method: 'get',
-                    asynchronous: false   //must be synchronous call to return control here
-                  });
+            url = this.getFullRequestString( getVisParams );
+            
+            OpenLayers.Request.GET({url: url, async: false});
           }
           
           //construct the full URL
@@ -15113,7 +35924,7 @@
                            tilerow: rowidx,
                            scaleindex: this.resolutions.length - this.map.zoom - 1
                         });
-         }
+           }
         
         return url;
     },
@@ -15268,12 +36079,9 @@
         newArguments.push(name, url, params, options);
         OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
 
-        if (arguments.length > 0) {
-            OpenLayers.Util.applyDefaults(
-                           this.params,
-                           this.DEFAULT_PARAMS
-                           );
-        }
+        this.params = OpenLayers.Util.applyDefaults(
+            this.params, this.DEFAULT_PARAMS
+        );
 
         // unless explicitly set in options, if the layer is transparent, 
         // it will be an overlay
@@ -15426,6 +36234,334 @@
     CLASS_NAME: "OpenLayers.Layer.MapServer"
 });
 /* ======================================================================
+    OpenLayers/Layer/TMS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * licence.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.TMS
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.TMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+    /**
+     * APIProperty: serviceVersion
+     * {String}
+     */
+    serviceVersion: "1.0.0",
+
+    /**
+     * APIProperty: isBaseLayer
+     * {Boolean}
+     */
+    isBaseLayer: true,
+
+    /**
+     * APIProperty: tileOrigin
+     * {<OpenLayers.Pixel>}
+     */
+    tileOrigin: null,
+
+    /**
+     * Constructor: OpenLayers.Layer.TMS
+     * 
+     * Parameters:
+     * name - {String}
+     * url - {String}
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, url, options) {
+        var newArguments = [];
+        newArguments.push(name, url, {}, options);
+        OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+    },    
+
+    /**
+     * APIMethod:destroy
+     */
+    destroy: function() {
+        // for now, nothing special to do here. 
+        OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments);  
+    },
+
+    
+    /**
+     * APIMethod: clone
+     * 
+     * Parameters:
+     * obj - {Object}
+     * 
+     * Returns:
+     * {<OpenLayers.Layer.TMS>} An exact clone of this <OpenLayers.Layer.TMS>
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.TMS(this.name,
+                                           this.url,
+                                           this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    },    
+    
+    /**
+     * Method: getURL
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * 
+     * Returns:
+     * {String} A string with the layer's url and parameters and also the 
+     *          passed-in bounds and appropriate tile size specified as 
+     *          parameters
+     */
+    getURL: function (bounds) {
+        bounds = this.adjustBounds(bounds);
+        var res = this.map.getResolution();
+        var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w));
+        var y = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h));
+        var z = this.map.getZoom();
+        var path = this.serviceVersion + "/" + this.layername + "/" + z + "/" + x + "/" + y + "." + this.type; 
+        var url = this.url;
+        if (url instanceof Array) {
+            url = this.selectUrl(path, url);
+        }
+        return url + path;
+    },
+
+    /**
+     * Method: addTile
+     * addTile creates a tile, initializes it, and adds it to the layer div. 
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * position - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {<OpenLayers.Tile.Image>} The added OpenLayers.Tile.Image
+     */
+    addTile:function(bounds,position) {
+        return new OpenLayers.Tile.Image(this, position, bounds, 
+                                         null, this.tileSize);
+    },
+
+    /** 
+     * APIMethod: setMap
+     * When the layer is added to a map, then we can fetch our origin 
+     *    (if we don't have one.) 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    setMap: function(map) {
+        OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+        if (!this.tileOrigin) { 
+            this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left,
+                                                this.map.maxExtent.bottom);
+        }                                       
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.TMS"
+});
+/* ======================================================================
+    OpenLayers/Layer/TileCache.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * licence.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.TileCache
+ * A read only TileCache layer.  Used to requests tiles cached by TileCache in
+ *     a web accessible cache.  This means that you have to pre-populate your
+ *     cache before this layer can be used.  It is meant only to read tiles
+ *     created by TileCache, and not to make calls to TileCache for tile
+ *     creation.  Create a new instance with the
+ *     <OpenLayers.Layer.TileCache> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.TileCache = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+    /** 
+     * APIProperty: isBaseLayer
+     * {Boolean} Treat this layer as a base layer.  Default is true.
+     */
+    isBaseLayer: true,
+    
+    /**
+     * APIProperty: tileOrigin
+     * {<OpenLayers.LonLat>} Location of the tile lattice origin.  Default is
+     *     bottom left of the maxExtent.
+     */
+    tileOrigin: null,
+
+    /** 
+     * APIProperty: format
+     * {String} Mime type of the images returned.  Default is image/png.
+     */
+    format: 'image/png',
+
+    /**
+     * Constructor: OpenLayers.Layer.TileCache
+     * Create a new read only TileCache layer.
+     *
+     * Parameters:
+     * name - {String} Name of the layer displayed in the interface
+     * url - {String} Location of the web accessible cache (not the location of
+     *     your tilecache script!)
+     * layername - {String} Layer name as defined in the TileCache 
+     *     configuration
+     * options - {Object} Optional object with properties to be set on the
+     *     layer.  Note that you should speficy your resolutions to match
+     *     your TileCache configuration.  This can be done by setting
+     *     the resolutions array directly (here or on the map), by setting
+     *     maxResolution and numZoomLevels, or by using scale based properties.
+     */
+    initialize: function(name, url, layername, options) {
+        this.layername = layername;
+        OpenLayers.Layer.Grid.prototype.initialize.apply(this,
+                                                         [name, url, {}, options]);
+        this.extension = this.format.split('/')[1].toLowerCase();
+        this.extension = (this.extension == 'jpg') ? 'jpeg' : this.extension;
+    },    
+
+    /**
+     * APIMethod: clone
+     * obj - {Object} 
+     * 
+     * Returns:
+     * {<OpenLayers.Layer.TileCache>} An exact clone of this 
+     *     <OpenLayers.Layer.TileCache>
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.TileCache(this.name,
+                                                 this.url,
+                                                 this.layername,
+                                                 this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    },    
+    
+    /**
+     * Method: getURL
+     *
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     * 
+     * Returns:
+     * {String} A string with the layer's url and parameters and also the 
+     *     passed-in bounds and appropriate tile size specified as parameters.
+     */
+    getURL: function(bounds) {
+        var res = this.map.getResolution();
+        var bbox = this.maxExtent;
+        var size = this.tileSize;
+        var tileX = Math.round((bounds.left - bbox.left) / (res * size.w));
+        var tileY = Math.round((bounds.bottom - bbox.bottom) / (res * size.h));
+        var tileZ = this.map.zoom;
+        /**
+         * Zero-pad a positive integer.
+         * number - {Int} 
+         * length - {Int} 
+         *
+         * Returns:
+         * {String} A zero-padded string
+         */
+        function zeroPad(number, length) {
+            number = String(number);
+            var zeros = [];
+            for(var i=0; i<length; ++i) {
+                zeros.push('0');
+            }
+            return zeros.join('').substring(0, length - number.length) + number;
+        }
+        var components = [
+            this.layername,
+            zeroPad(tileZ, 2),
+            zeroPad(parseInt(tileX / 1000000), 3),
+            zeroPad((parseInt(tileX / 1000) % 1000), 3),
+            zeroPad((parseInt(tileX) % 1000), 3),
+            zeroPad(parseInt(tileY / 1000000), 3),
+            zeroPad((parseInt(tileY / 1000) % 1000), 3),
+            zeroPad((parseInt(tileY) % 1000), 3) + '.' + this.extension
+        ];
+        var path = components.join('/'); 
+        var url = this.url;
+        if (url instanceof Array) {
+            url = this.selectUrl(path, url);
+        }
+        url = (url.charAt(url.length - 1) == '/') ? url : url + '/';
+        return url + path;
+    },
+
+    /**
+     * Method: addTile
+     * Create a tile, initialize it, and add it to the layer div. 
+     *
+     * Parameters: 
+     * bounds - {<OpenLayers.Bounds>} 
+     * position - {<OpenLayers.Pixel>}
+     *
+     * Returns:
+     * {<OpenLayers.Tile.Image>} The added <OpenLayers.Tile.Image>
+     */
+    addTile:function(bounds, position) {
+        var url = this.getURL(bounds);
+        return new OpenLayers.Tile.Image(this, position, bounds, 
+                                             url, this.tileSize);
+    },
+
+    /** 
+     * Method: setMap
+     * When the layer is added to a map, then we can fetch our origin 
+     *     (if we don't have one.) 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+        if (!this.tileOrigin) { 
+            this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left,
+                                                    this.map.maxExtent.bottom);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.TileCache"
+});
+/* ======================================================================
     OpenLayers/Layer/WMS.js
    ====================================================================== */
 
@@ -15617,7 +36753,8 @@
      * Catch changeParams and uppercase the new params to be merged in
      *     before calling changeParams on the super class.
      * 
-     *     Once params have been changed, we will need to re-init our tiles.
+     *     Once params have been changed, the tiles will be reloaded with
+     *     the new parameters.
      * 
      * Parameters:
      * newParams - {Object} Hashtable of new params to use
@@ -15630,7 +36767,7 @@
     },
 
     /** 
-     * Method: getFullRequestString
+     * APIMethod: getFullRequestString
      * Combine the layer's url with its params and these newParams. 
      *   
      *     Add the SRS parameter from projection -- this is probably
@@ -15654,3 +36791,10969 @@
 
     CLASS_NAME: "OpenLayers.Layer.WMS"
 });
+/* ======================================================================
+    OpenLayers/Layer/WorldWind.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WorldWind
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WorldWind = OpenLayers.Class(OpenLayers.Layer.Grid, {
+    
+    DEFAULT_PARAMS: {
+    },
+
+    /**
+     * APIProperty: isBaseLayer
+     * WorldWind layer is a base layer by default.
+     */
+    isBaseLayer: true,    
+
+    
+    /** 
+     * APIProperty: lzd
+     * LevelZeroTileSizeDegrees
+     */
+    lzd: null,
+
+    /**
+     * APIProperty: zoomLevels
+     * Number of zoom levels.
+     */
+    zoomLevels: null,
+    
+    /**
+     * Constructor: OpenLayers.Layer.WorldWind
+     * 
+     * Parameters:
+     * name - {String} Name of Layer
+     * url - {String} Base URL  
+     * lzd - {Float} Level zero tile size degrees 
+     * zoomLevels - {Int} number of zoom levels
+     * params - {Object} additional parameters
+     * options - {Object} additional options
+     */
+    initialize: function(name, url, lzd, zoomLevels, params, options) {
+        this.lzd = lzd;
+        this.zoomLevels = zoomLevels;
+        var newArguments = [];
+        newArguments.push(name, url, params, options);
+        OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+        this.params = OpenLayers.Util.applyDefaults(
+            this.params, this.DEFAULT_PARAMS
+        );
+    },
+    /**
+     * Method: addTile
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * position - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {<OpenLayers.Tile.Image>} The added OpenLayers.Tile.Image
+     */
+    addTile:function(bounds,position) {
+        return new OpenLayers.Tile.Image(this, position, bounds, 
+                                             null, this.tileSize);
+    },
+
+    /**
+     * Method: getZoom
+     * Convert map zoom to WW zoom.
+     */
+    getZoom: function () {
+        var zoom = this.map.getZoom();
+        var extent = this.map.getMaxExtent();
+        zoom = zoom - Math.log(this.maxResolution / (this.lzd/512))/Math.log(2);
+        return zoom;
+    },
+
+    /**
+     * Method: getURL
+     *
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     *
+     * Returns:
+     * {String} A string with the layer's url and parameters and also the 
+     *           passed-in bounds and appropriate tile size specified as 
+     *           parameters
+     */
+    getURL: function (bounds) {
+        bounds = this.adjustBounds(bounds);
+        var zoom = this.getZoom();
+        var extent = this.map.getMaxExtent();
+        var deg = this.lzd/Math.pow(2,this.getZoom());
+        var x = Math.floor((bounds.left - extent.left)/deg);
+        var y = Math.floor((bounds.bottom - extent.bottom)/deg);
+        if (this.map.getResolution() <= (this.lzd/512)
+            && this.getZoom() <= this.zoomLevels) {
+            return this.getFullRequestString(
+              { L: zoom, 
+                X: x,
+                Y: y
+              });
+        } else {
+            return OpenLayers.Util.getImagesLocation() + "blank.gif";
+        }
+
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.WorldWind"
+});
+/* ======================================================================
+    OpenLayers/Rule.js
+   ====================================================================== */
+
+/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Rule
+ * This class represents an SLD Rule, as being used for rule-based SLD styling.
+ */
+OpenLayers.Rule = OpenLayers.Class({
+    
+    /**
+     * Property: id
+     * {String} A unique id for this session.
+     */
+    id: null,
+    
+    /**
+     * APIProperty: name
+     * {String} name of this rule
+     */
+    name: 'default',
+    
+    /**
+     * Property: title
+     * {String} Title of this rule (set if included in SLD)
+     */
+    title: null,
+    
+    /**
+     * Property: description
+     * {String} Description of this rule (set if abstract is included in SLD)
+     */
+    description: null,
+
+    /**
+     * Property: context
+     * {Object} An optional object with properties that the rule should be
+     * evaluated against. If no context is specified, feature.attributes will
+     * be used.
+     */
+    context: null,
+    
+    /**
+     * Property: filter
+     * {<OpenLayers.Filter>} Optional filter for the rule.
+     */
+    filter: null,
+
+    /**
+     * Property: elseFilter
+     * {Boolean} Determines whether this rule is only to be applied only if
+     * no other rules match (ElseFilter according to the SLD specification). 
+     * Default is false.  For instances of OpenLayers.Rule, if elseFilter is
+     * false, the rule will always apply.  For subclasses, the else property is 
+     * ignored.
+     */
+    elseFilter: false,
+    
+    /**
+     * Property: symbolizer
+     * {Object} Symbolizer or hash of symbolizers for this rule. If hash of
+     * symbolizers, keys are one or more of ["Point", "Line", "Polygon"]. The
+     * latter if useful if it is required to style e.g. vertices of a line
+     * with a point symbolizer. Note, however, that this is not implemented
+     * yet in OpenLayers, but it is the way how symbolizers are defined in
+     * SLD.
+     */
+    symbolizer: null,
+    
+    /**
+     * APIProperty: minScaleDenominator
+     * {Number} or {String} minimum scale at which to draw the feature.
+     * In the case of a String, this can be a combination of text and
+     * propertyNames in the form "literal ${propertyName}"
+     */
+    minScaleDenominator: null,
+
+    /**
+     * APIProperty: maxScaleDenominator
+     * {Number} or {String} maximum scale at which to draw the feature.
+     * In the case of a String, this can be a combination of text and
+     * propertyNames in the form "literal ${propertyName}"
+     */
+    maxScaleDenominator: null,
+    
+    /** 
+     * Constructor: OpenLayers.Rule
+     * Creates a Rule.
+     *
+     * Parameters:
+     * options - {Object} An optional object with properties to set on the
+     *           rule
+     * 
+     * Returns:
+     * {<OpenLayers.Rule>}
+     */
+    initialize: function(options) {
+        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+        this.symbolizer = {};
+
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /** 
+     * APIMethod: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+        for (var i in this.symbolizer) {
+            this.symbolizer[i] = null;
+        }
+        this.symbolizer = null;
+    },
+    
+    /**
+     * APIMethod: evaluate
+     * evaluates this rule for a specific feature
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature>} feature to apply the rule to.
+     * 
+     * Returns:
+     * {Boolean} true if the rule applies, false if it does not.
+     * This rule is the default rule and always returns true.
+     */
+    evaluate: function(feature) {
+        var context = this.getContext(feature);
+        var applies = true;
+
+        if (this.minScaleDenominator || this.maxScaleDenominator) {
+            var scale = feature.layer.map.getScale();
+        }
+        
+        // check if within minScale/maxScale bounds
+        if (this.minScaleDenominator) {
+            applies = scale >= OpenLayers.Style.createLiteral(
+                    this.minScaleDenominator, context);
+        }
+        if (applies && this.maxScaleDenominator) {
+            applies = scale < OpenLayers.Style.createLiteral(
+                    this.maxScaleDenominator, context);
+        }
+        
+        // check if optional filter applies
+        if(applies && this.filter) {
+            // feature id filters get the feature, others get the context
+            if(this.filter.CLASS_NAME == "OpenLayers.Filter.FeatureId") {
+                applies = this.filter.evaluate(feature);
+            } else {
+                applies = this.filter.evaluate(context);
+            }
+        }
+
+        return applies;
+    },
+    
+    /**
+     * Method: getContext
+     * Gets the context for evaluating this rule
+     * 
+     * Paramters:
+     * feature - {<OpenLayers.Feature>} feature to take the context from if
+     *           none is specified.
+     */
+    getContext: function(feature) {
+        var context = this.context;
+        if (!context) {
+            context = feature.attributes || feature.data;
+        }
+        if (typeof this.context == "function") {
+            context = this.context(feature);
+        }
+        return context;
+    },
+        
+    CLASS_NAME: "OpenLayers.Rule"
+});
+/* ======================================================================
+    OpenLayers/StyleMap.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Style.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+ 
+/**
+ * Class: OpenLayers.StyleMap
+ */
+OpenLayers.StyleMap = OpenLayers.Class({
+    
+    /**
+     * Property: styles
+     * Hash of {<OpenLayers.Style>}, keyed by names of well known
+     * rendering intents (e.g. "default", "temporary", "select").
+     */
+    styles: null,
+    
+    /**
+     * Property: extendDefault
+     * {Boolean} if true, every render intent will extend the symbolizers
+     * specified for the "default" intent at rendering time. Otherwise, every
+     * rendering intent will be treated as a completely independent style.
+     */
+    extendDefault: true,
+    
+    /**
+     * Constructor: OpenLayers.StyleMap
+     * 
+     * Parameters:
+     * style   - {Object} Optional. Either a style hash, or a style object, or
+     *           a hash of style objects (style hashes) keyed by rendering
+     *           intent. If just one style hash or style object is passed,
+     *           this will be used for all known render intents (default,
+     *           select, temporary)
+     * options - {Object} optional hash of additional options for this
+     *           instance
+     */
+    initialize: function (style, options) {
+        this.styles = {
+            "default": new OpenLayers.Style(
+                OpenLayers.Feature.Vector.style["default"]),
+            "select": new OpenLayers.Style(
+                OpenLayers.Feature.Vector.style["select"]),
+            "temporary": new OpenLayers.Style(
+                OpenLayers.Feature.Vector.style["temporary"])
+        };
+        
+        // take whatever the user passed as style parameter and convert it
+        // into parts of stylemap.
+        if(style instanceof OpenLayers.Style) {
+            // user passed a style object
+            this.styles["default"] = style;
+            this.styles["select"] = style;
+            this.styles["temporary"] = style;
+        } else if(typeof style == "object") {
+            for(var key in style) {
+                if(style[key] instanceof OpenLayers.Style) {
+                    // user passed a hash of style objects
+                    this.styles[key] = style[key];
+                } else if(typeof style[key] == "object") {
+                    // user passsed a hash of style hashes
+                    this.styles[key] = new OpenLayers.Style(style[key]);
+                } else {
+                    // user passed a style hash (i.e. symbolizer)
+                    this.styles["default"] = new OpenLayers.Style(style);
+                    this.styles["select"] = new OpenLayers.Style(style);
+                    this.styles["temporary"] = new OpenLayers.Style(style);
+                    break;
+                }
+            }
+        }
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /**
+     * Method: destroy
+     */
+    destroy: function() {
+        for(var key in this.styles) {
+            this.styles[key].destroy();
+        }
+        this.styles = null;
+    },
+    
+    /**
+     * Method: createSymbolizer
+     * Creates the symbolizer for a feature for a render intent.
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature>} The feature to evaluate the rules
+     *           of the intended style against.
+     * intent  - {String} The intent determines the symbolizer that will be
+     *           used to draw the feature. Well known intents are "default"
+     *           (for just drawing the features), "select" (for selected
+     *           features) and "temporary" (for drawing features).
+     * 
+     * Returns:
+     * {Object} symbolizer hash
+     */
+    createSymbolizer: function(feature, intent) {
+        if(!feature) {
+            feature = new OpenLayers.Feature.Vector();
+        }
+        if(!this.styles[intent]) {
+            intent = "default";
+        }
+        feature.renderIntent = intent;
+        var defaultSymbolizer = {};
+        if(this.extendDefault && intent != "default") {
+            defaultSymbolizer = this.styles["default"].createSymbolizer(feature);
+        }
+        return OpenLayers.Util.extend(defaultSymbolizer,
+            this.styles[intent].createSymbolizer(feature));
+    },
+    
+    /**
+     * Method: addUniqueValueRules
+     * Convenience method to create comparison rules for unique values of a
+     * property. The rules will be added to the style object for a specified
+     * rendering intent. This method is a shortcut for creating something like
+     * the "unique value legends" familiar from well known desktop GIS systems
+     * 
+     * Parameters:
+     * renderIntent - {String} rendering intent to add the rules to
+     * property     - {String} values of feature attributes to create the
+     *                rules for
+     * symbolizers  - {Object} Hash of symbolizers, keyed by the desired
+     *                property values 
+     * context      - {Object} An optional object with properties that
+     *                symbolizers' property values should be evaluated
+     *                against. If no context is specified, feature.attributes
+     *                will be used
+     */
+    addUniqueValueRules: function(renderIntent, property, symbolizers, context) {
+        var rules = [];
+        for (var value in symbolizers) {
+            rules.push(new OpenLayers.Rule({
+                symbolizer: symbolizers[value],
+                context: context,
+                filter: new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.EQUAL_TO,
+                    property: property,
+                    value: value
+                })
+            }));
+        }
+        this.styles[renderIntent].addRules(rules);
+    },
+
+    CLASS_NAME: "OpenLayers.StyleMap"
+});
+/* ======================================================================
+    OpenLayers/Control/NavToolbar.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/Navigation.js
+ * @requires OpenLayers/Control/ZoomBox.js
+ */
+
+/**
+ * Class: OpenLayers.Control.NavToolbar
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control.Panel>
+ */
+OpenLayers.Control.NavToolbar = OpenLayers.Class(OpenLayers.Control.Panel, {
+
+    /**
+     * Constructor: OpenLayers.Control.NavToolbar 
+     * Add our two mousedefaults controls.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be used
+     *     to extend the control.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+        this.addControls([
+          new OpenLayers.Control.Navigation(),
+          new OpenLayers.Control.ZoomBox()
+        ]);
+    },
+
+    /**
+     * Method: draw 
+     * calls the default draw, and then activates mouse defaults.
+     */
+    draw: function() {
+        var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments);
+        this.activateControl(this.controls[0]);
+        return div;
+    },
+
+    CLASS_NAME: "OpenLayers.Control.NavToolbar"
+});
+/* ======================================================================
+    OpenLayers/Filter/Comparison.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+  * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Comparison
+ * This class represents a comparison filter.
+ * 
+ * Inherits from
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Comparison = OpenLayers.Class(OpenLayers.Filter, {
+
+    /**
+     * APIProperty: type
+     * {String} type: type of the comparison. This is one of
+     * - OpenLayers.Filter.Comparison.EQUAL_TO                 = "==";
+     * - OpenLayers.Filter.Comparison.NOT_EQUAL_TO             = "!=";
+     * - OpenLayers.Filter.Comparison.LESS_THAN                = "<";
+     * - OpenLayers.Filter.Comparison.GREATER_THAN             = ">";
+     * - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO    = "<=";
+     * - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+     * - OpenLayers.Filter.Comparison.BETWEEN                  = "..";
+     * - OpenLayers.Filter.Comparison.LIKE                     = "~"; 
+     */
+    type: null,
+    
+    /**
+     * APIProperty: property
+     * {String}
+     * name of the context property to compare
+     */
+    property: null,
+    
+    /**
+     * APIProperty: value
+     * {Number} or {String}
+     * comparison value for binary comparisons. In the case of a String, this
+     * can be a combination of text and propertyNames in the form
+     * "literal ${propertyName}"
+     */
+    value: null,
+    
+    /**
+     * APIProperty: lowerBoundary
+     * {Number} or {String}
+     * lower boundary for between comparisons. In the case of a String, this
+     * can be a combination of text and propertyNames in the form
+     * "literal ${propertyName}"
+     */
+    lowerBoundary: null,
+    
+    /**
+     * APIProperty: upperBoundary
+     * {Number} or {String}
+     * upper boundary for between comparisons. In the case of a String, this
+     * can be a combination of text and propertyNames in the form
+     * "literal ${propertyName}"
+     */
+    upperBoundary: null,
+
+    /** 
+     * Constructor: OpenLayers.Filter.Comparison
+     * Creates a comparison rule.
+     *
+     * Parameters:
+     * options - {Object} An optional object with properties to set on the
+     *           rule
+     * 
+     * Returns:
+     * {<OpenLayers.Filter.Comparison>}
+     */
+    initialize: function(options) {
+        OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: evaluate
+     * Evaluates this filter in a specific context.  Should be implemented by
+     *     subclasses.
+     * 
+     * Parameters:
+     * context - {Object} Context to use in evaluating the filter.
+     * 
+     * Returns:
+     * {Boolean} The filter applies.
+     */
+    evaluate: function(context) {
+        switch(this.type) {
+            case OpenLayers.Filter.Comparison.EQUAL_TO:
+            case OpenLayers.Filter.Comparison.LESS_THAN:
+            case OpenLayers.Filter.Comparison.GREATER_THAN:
+            case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
+            case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
+                return this.binaryCompare(context, this.property, this.value);
+            
+            case OpenLayers.Filter.Comparison.BETWEEN:
+                var result =
+                        context[this.property] >= this.lowerBoundary;
+                result = result &&
+                        context[this.property] <= this.upperBoundary;
+                return result;
+            case OpenLayers.Filter.Comparison.LIKE:
+                var regexp = new RegExp(this.value,
+                                "gi");
+                return regexp.test(context[this.property]); 
+        }
+    },
+    
+    /**
+     * APIMethod: value2regex
+     * Converts the value of this rule into a regular expression string,
+     * according to the wildcard characters specified. This method has to
+     * be called after instantiation of this class, if the value is not a
+     * regular expression already.
+     * 
+     * Parameters:
+     * wildCard   - {<Char>} wildcard character in the above value, default
+     *              is "*"
+     * singleChar - {<Char>) single-character wildcard in the above value
+     *              default is "."
+     * escape     - {<Char>) escape character in the above value, default is
+     *              "!"
+     * 
+     * Returns:
+     * {String} regular expression string
+     */
+    value2regex: function(wildCard, singleChar, escapeChar) {
+        if (wildCard == ".") {
+            var msg = "'.' is an unsupported wildCard character for "+
+                    "OpenLayers.Filter.Comparison";
+            OpenLayers.Console.error(msg);
+            return null;
+        }
+        
+
+        // set UMN MapServer defaults for unspecified parameters
+        wildCard = wildCard ? wildCard : "*";
+        singleChar = singleChar ? singleChar : ".";
+        escapeChar = escapeChar ? escapeChar : "!";
+        
+        this.value = this.value.replace(
+                new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1")
+        this.value = this.value.replace(
+                new RegExp("\\"+singleChar, "g"), ".");
+        this.value = this.value.replace(
+                new RegExp("\\"+wildCard, "g"), ".*");
+        this.value = this.value.replace(
+                new RegExp("\\\\.\\*", "g"), "\\"+wildCard);
+        this.value = this.value.replace(
+                new RegExp("\\\\\\.", "g"), "\\"+singleChar);
+        
+        return this.value;
+    },
+    
+    /**
+     * Method: regex2value
+     * Convert the value of this rule from a regular expression string into an
+     *     ogc literal string using a wildCard of *, a singleChar of ., and an
+     *     escape of !.  Leaves the <value> property unmodified.
+     * 
+     * Returns:
+     * {String} A string value.
+     */
+    regex2value: function() {
+        
+        var value = this.value;
+        
+        // replace ! with !!
+        value = value.replace(/!/g, "!!");
+
+        // replace \. with !. (watching out for \\.)
+        value = value.replace(/(\\)?\\\./g, function($0, $1) {
+            return $1 ? $0 : "!.";
+        });
+        
+        // replace \* with #* (watching out for \\*)
+        value = value.replace(/(\\)?\\\*/g, function($0, $1) {
+            return $1 ? $0 : "!*";
+        });
+        
+        // replace \\ with \
+        value = value.replace(/\\\\/g, "\\");
+
+        // convert .* to * (the sequence #.* is not allowed)
+        value = value.replace(/\.\*/g, "*");
+        
+        return value;
+    },
+
+    /**
+     * Function: binaryCompare
+     * Compares a feature property to a rule value
+     * 
+     * Parameters:
+     * context  - {Object}
+     * property - {String} or {Number}
+     * value    - {String} or {Number}, same as property
+     * 
+     * Returns:
+     * {Boolean}
+     */
+    binaryCompare: function(context, property, value) {
+        switch (this.type) {
+            case OpenLayers.Filter.Comparison.EQUAL_TO:
+                return context[property] == value;
+            case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
+                return context[property] != value;
+            case OpenLayers.Filter.Comparison.LESS_THAN:
+                return context[property] < value;
+            case OpenLayers.Filter.Comparison.GREATER_THAN:
+                return context[property] > value;
+            case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
+                return context[property] <= value;
+            case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
+                return context[property] >= value;
+        }      
+    },
+    
+    CLASS_NAME: "OpenLayers.Filter.Comparison"
+});
+
+
+OpenLayers.Filter.Comparison.EQUAL_TO                 = "==";
+OpenLayers.Filter.Comparison.NOT_EQUAL_TO             = "!=";
+OpenLayers.Filter.Comparison.LESS_THAN                = "<";
+OpenLayers.Filter.Comparison.GREATER_THAN             = ">";
+OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO    = "<=";
+OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+OpenLayers.Filter.Comparison.BETWEEN                  = "..";
+OpenLayers.Filter.Comparison.LIKE                     = "~";
+/* ======================================================================
+    OpenLayers/Filter/FeatureId.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+  * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.FeatureId
+ * This class represents a ogc:FeatureId Filter, as being used for rule-based SLD
+ * styling
+ * 
+ * Inherits from
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.FeatureId = OpenLayers.Class(OpenLayers.Filter, {
+
+    /** 
+     * APIProperty: fids
+     * {Array(String)} Feature Ids to evaluate this rule against. To be passed
+     * To be passed inside the params object.
+     */
+    fids: null,
+    
+    /** 
+     * Constructor: OpenLayers.Filter.FeatureId
+     * Creates an ogc:FeatureId rule.
+     *
+     * Parameters:
+     * options - {Object} An optional object with properties to set on the
+     *           rule
+     * 
+     * Returns:
+     * {<OpenLayers.Filter.FeatureId>}
+     */
+    initialize: function(options) {
+        this.fids = [];
+        OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: evaluate
+     * evaluates this rule for a specific feature
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature>} feature to apply the rule to.
+     *           For vector features, the check is run against the fid,
+     *           for plain features against the id.
+     * 
+     * Returns:
+     * {Boolean} true if the rule applies, false if it does not
+     */
+    evaluate: function(feature) {
+        for (var i=0, len=this.fids.length; i<len; i++) {
+            var fid = feature.fid || feature.id;
+            if (fid == this.fids[i]) {
+                return true;
+            }
+        }
+        return false;
+    },
+    
+    CLASS_NAME: "OpenLayers.Filter.FeatureId"
+});
+/* ======================================================================
+    OpenLayers/Filter/Logical.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+  * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Logical
+ * This class represents ogc:And, ogc:Or and ogc:Not rules.
+ * 
+ * Inherits from
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, {
+
+    /**
+     * APIProperty: filters
+     * {Array(<OpenLayers.Filter>)} Child filters for this filter.
+     */
+    filters: null, 
+     
+    /**
+     * APIProperty: type
+     * {String} type of logical operator. Available types are:
+     * - OpenLayers.Filter.Locical.AND = "&&";
+     * - OpenLayers.Filter.Logical.OR  = "||";
+     * - OpenLayers.Filter.Logical.NOT = "!";
+     */
+    type: null,
+
+    /** 
+     * Constructor: OpenLayers.Filter.Logical
+     * Creates a logical filter (And, Or, Not).
+     *
+     * Parameters:
+     * options - {Object} An optional object with properties to set on the
+     *     filter.
+     * 
+     * Returns:
+     * {<OpenLayers.Filter.Logical>}
+     */
+    initialize: function(options) {
+        this.filters = [];
+        OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+    },
+    
+    /** 
+     * APIMethod: destroy
+     * Remove reference to child filters.
+     */
+    destroy: function() {
+        this.filters = null;
+        OpenLayers.Filter.prototype.destroy.apply(this);
+    },
+
+    /**
+     * APIMethod: evaluate
+     * Evaluates this filter in a specific context.  Should be implemented by
+     *     subclasses.
+     * 
+     * Parameters:
+     * context - {Object} Context to use in evaluating the filter.
+     * 
+     * Returns:
+     * {Boolean} The filter applies.
+     */
+    evaluate: function(context) {
+        switch(this.type) {
+            case OpenLayers.Filter.Logical.AND:
+                for (var i=0, len=this.filters.length; i<len; i++) {
+                    if (this.filters[i].evaluate(context) == false) {
+                        return false;
+                    }
+                }
+                return true;
+                
+            case OpenLayers.Filter.Logical.OR:
+                for (var i=0, len=this.filters.length; i<len; i++) {
+                    if (this.filters[i].evaluate(context) == true) {
+                        return true;
+                    }
+                }
+                return false;
+            
+            case OpenLayers.Filter.Logical.NOT:
+                return (!this.filters[0].evaluate(context));
+        }
+    },
+    
+    CLASS_NAME: "OpenLayers.Filter.Logical"
+});
+
+
+OpenLayers.Filter.Logical.AND = "&&";
+OpenLayers.Filter.Logical.OR  = "||";
+OpenLayers.Filter.Logical.NOT = "!";
+/* ======================================================================
+    OpenLayers/Geometry/Collection.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Collection
+ * A Collection is exactly what it sounds like: A collection of different 
+ * Geometries. These are stored in the local parameter <components> (which
+ * can be passed as a parameter to the constructor). 
+ * 
+ * As new geometries are added to the collection, they are NOT cloned. 
+ * When removing geometries, they need to be specified by reference (ie you 
+ * have to pass in the *exact* geometry to be removed).
+ * 
+ * The <getArea> and <getLength> functions here merely iterate through
+ * the components, summing their respective areas and lengths.
+ *
+ * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
+ *
+ * Inerhits from:
+ *  - <OpenLayers.Geometry> 
+ */
+OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
+
+    /**
+     * APIProperty: components
+     * {Array(<OpenLayers.Geometry>)} The component parts of this geometry
+     */
+    components: null,
+    
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of
+     * components that the collection can include.  A null value means the
+     * component types are not restricted.
+     */
+    componentTypes: null,
+
+    /**
+     * Constructor: OpenLayers.Geometry.Collection
+     * Creates a Geometry Collection -- a list of geoms.
+     *
+     * Parameters: 
+     * components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
+     *
+     */
+    initialize: function (components) {
+        OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+        this.components = [];
+        if (components != null) {
+            this.addComponents(components);
+        }
+    },
+
+    /**
+     * APIMethod: destroy
+     * Destroy this geometry.
+     */
+    destroy: function () {
+        this.components.length = 0;
+        this.components = null;
+    },
+
+    /**
+     * APIMethod: clone
+     * Clone this geometry.
+     *
+     * Returns:
+     * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
+     */
+    clone: function() {
+        var geometry = eval("new " + this.CLASS_NAME + "()");
+        for(var i=0, len=this.components.length; i<len; i++) {
+            geometry.addComponent(this.components[i].clone());
+        }
+        
+        // catch any randomly tagged-on properties
+        OpenLayers.Util.applyDefaults(geometry, this);
+        
+        return geometry;
+    },
+
+    /**
+     * Method: getComponentsString
+     * Get a string representing the components for this collection
+     * 
+     * Returns:
+     * {String} A string representation of the components of this geometry
+     */
+    getComponentsString: function(){
+        var strings = [];
+        for(var i=0, len=this.components.length; i<len; i++) {
+            strings.push(this.components[i].toShortString()); 
+        }
+        return strings.join(",");
+    },
+
+    /**
+     * APIMethod: calculateBounds
+     * Recalculate the bounds by iterating through the components and 
+     * calling calling extendBounds() on each item.
+     */
+    calculateBounds: function() {
+        this.bounds = null;
+        if ( this.components && this.components.length > 0) {
+            this.setBounds(this.components[0].getBounds());
+            for (var i=1, len=this.components.length; i<len; i++) {
+                this.extendBounds(this.components[i].getBounds());
+            }
+        }
+    },
+
+    /**
+     * APIMethod: addComponents
+     * Add components to this geometry.
+     *
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
+     */
+    addComponents: function(components){
+        if(!(components instanceof Array)) {
+            components = [components];
+        }
+        for(var i=0, len=components.length; i<len; i++) {
+            this.addComponent(components[i]);
+        }
+    },
+
+    /**
+     * Method: addComponent
+     * Add a new component (geometry) to the collection.  If this.componentTypes
+     * is set, then the component class name must be in the componentTypes array.
+     *
+     * The bounds cache is reset.
+     * 
+     * Parameters:
+     * component - {<OpenLayers.Geometry>} A geometry to add
+     * index - {int} Optional index into the array to insert the component
+     *
+     * Returns:
+     * {Boolean} The component geometry was successfully added
+     */    
+    addComponent: function(component, index) {
+        var added = false;
+        if(component) {
+            if(this.componentTypes == null ||
+               (OpenLayers.Util.indexOf(this.componentTypes,
+                                        component.CLASS_NAME) > -1)) {
+
+                if(index != null && (index < this.components.length)) {
+                    var components1 = this.components.slice(0, index);
+                    var components2 = this.components.slice(index, 
+                                                           this.components.length);
+                    components1.push(component);
+                    this.components = components1.concat(components2);
+                } else {
+                    this.components.push(component);
+                }
+                component.parent = this;
+                this.clearBounds();
+                added = true;
+            }
+        }
+        return added;
+    },
+    
+    /**
+     * APIMethod: removeComponents
+     * Remove components from this geometry.
+     *
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry>)} The components to be removed
+     */
+    removeComponents: function(components) {
+        if(!(components instanceof Array)) {
+            components = [components];
+        }
+        for(var i=components.length-1; i>=0; --i) {
+            this.removeComponent(components[i]);
+        }
+    },
+    
+    /**
+     * Method: removeComponent
+     * Remove a component from this geometry.
+     *
+     * Parameters:
+     * component - {<OpenLayers.Geometry>} 
+     */
+    removeComponent: function(component) {
+        
+        OpenLayers.Util.removeItem(this.components, component);
+        
+        // clearBounds() so that it gets recalculated on the next call
+        // to this.getBounds();
+        this.clearBounds();
+    },
+
+    /**
+     * APIMethod: getLength
+     * Calculate the length of this geometry
+     *
+     * Returns:
+     * {Float} The length of the geometry
+     */
+    getLength: function() {
+        var length = 0.0;
+        for (var i=0, len=this.components.length; i<len; i++) {
+            length += this.components[i].getLength();
+        }
+        return length;
+    },
+    
+    /**
+     * APIMethod: getArea
+     * Calculate the area of this geometry. Note how this function is overridden
+     * in <OpenLayers.Geometry.Polygon>.
+     *
+     * Returns:
+     * {Float} The area of the collection by summing its parts
+     */
+    getArea: function() {
+        var area = 0.0;
+        for (var i=0, len=this.components.length; i<len; i++) {
+            area += this.components[i].getArea();
+        }
+        return area;
+    },
+
+    /**
+     * APIMethod: move
+     * Moves a collection in place
+     *
+     * Parameters:
+     * x - {Float} The x-displacement (in map units)
+     * y - {Float} The y-displacement (in map units)
+     */
+    move: function(x, y) {
+        for(var i=0, len=this.components.length; i<len; i++) {
+            this.components[i].move(x, y);
+        }
+    },
+
+    /**
+     * APIMethod: rotate
+     * Rotate a geometry around some origin
+     *
+     * Parameters:
+     * angle - {Float} Rotation angle in degrees (measured counterclockwise
+     *                 from the positive x-axis)
+     * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+     */
+    rotate: function(angle, origin) {
+        for(var i=0, len=this.components.length; i<len; ++i) {
+            this.components[i].rotate(angle, origin);
+        }
+    },
+
+    /**
+     * APIMethod: resize
+     * Resize a geometry relative to some origin.  Use this method to apply
+     *     a uniform scaling to a geometry.
+     *
+     * Parameters:
+     * scale - {Float} Factor by which to scale the geometry.  A scale of 2
+     *                 doubles the size of the geometry in each dimension
+     *                 (lines, for example, will be twice as long, and polygons
+     *                 will have four times the area).
+     * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+     * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
+     */
+    resize: function(scale, origin, ratio) {
+        for(var i=0; i<this.components.length; ++i) {
+            this.components[i].resize(scale, origin, ratio);
+        }
+    },
+
+    /**
+     * APIMethod: equals
+     * Tests for equivalent geometries
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     *
+     * Returns:
+     * {Boolean} The coordinates are equivalent
+     */
+    equals: function(geometry) {
+        var equivalent = true;
+        if(!geometry || !geometry.CLASS_NAME ||
+           (this.CLASS_NAME != geometry.CLASS_NAME)) {
+            equivalent = false;
+        } else if(!(geometry.components instanceof Array) ||
+                  (geometry.components.length != this.components.length)) {
+            equivalent = false;
+        } else {
+            for(var i=0, len=this.components.length; i<len; ++i) {
+                if(!this.components[i].equals(geometry.components[i])) {
+                    equivalent = false;
+                    break;
+                }
+            }
+        }
+        return equivalent;
+    },
+
+    /**
+     * APIMethod: transform
+     * Reproject the components geometry from source to dest.
+     * 
+     * Parameters:
+     * source - {<OpenLayers.Projection>} 
+     * dest - {<OpenLayers.Projection>}
+     * 
+     * Returns:
+     * {<OpenLayers.Geometry>} 
+     */
+    transform: function(source, dest) {
+        if (source && dest) {
+            for (var i=0, len=this.components.length; i<len; i++) {  
+                var component = this.components[i];
+                component.transform(source, dest);
+            }
+            this.bounds = null;
+        }
+        return this;
+    },
+
+    /**
+     * APIMethod: intersects
+     * Determine if the input geometry intersects this one.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+     *
+     * Returns:
+     * {Boolean} The input geometry intersects this one.
+     */
+    intersects: function(geometry) {
+        var intersect = false;
+        for(var i=0, len=this.components.length; i<len; ++ i) {
+            intersect = geometry.intersects(this.components[i]);
+            if(intersect) {
+                break;
+            }
+        }
+        return intersect;
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.Collection"
+});
+/* ======================================================================
+    OpenLayers/Geometry/Point.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Point
+ * Point geometry class. 
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Geometry> 
+ */
+OpenLayers.Geometry.Point = OpenLayers.Class(OpenLayers.Geometry, {
+
+    /** 
+     * APIProperty: x 
+     * {float} 
+     */
+    x: null,
+
+    /** 
+     * APIProperty: y 
+     * {float} 
+     */
+    y: null,
+
+    /**
+     * Constructor: OpenLayers.Geometry.Point
+     * Construct a point geometry.
+     *
+     * Parameters:
+     * x - {float} 
+     * y - {float}
+     * 
+     */
+    initialize: function(x, y) {
+        OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+        
+        this.x = parseFloat(x);
+        this.y = parseFloat(y);
+    },
+
+    /**
+     * APIMethod: clone
+     * 
+     * Returns:
+     * {<OpenLayers.Geometry.Point>} An exact clone of this OpenLayers.Geometry.Point
+     */
+    clone: function(obj) {
+        if (obj == null) {
+            obj = new OpenLayers.Geometry.Point(this.x, this.y);
+        }
+
+        // catch any randomly tagged-on properties
+        OpenLayers.Util.applyDefaults(obj, this);
+
+        return obj;
+    },
+
+    /** 
+     * Method: calculateBounds
+     * Create a new Bounds based on the lon/lat
+     */
+    calculateBounds: function () {
+        this.bounds = new OpenLayers.Bounds(this.x, this.y,
+                                            this.x, this.y);
+    },
+
+    /**
+     * APIMethod: distanceTo
+     * 
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>} 
+     */
+    distanceTo: function(point) {
+        var distance = 0.0;
+        if ( (this.x != null) && (this.y != null) && 
+             (point != null) && (point.x != null) && (point.y != null) ) {
+             
+             var dx2 = Math.pow(this.x - point.x, 2);
+             var dy2 = Math.pow(this.y - point.y, 2);
+             distance = Math.sqrt( dx2 + dy2 );
+        }
+        return distance;
+    },
+    
+    /** 
+    * APIMethod: equals
+    * 
+    * Parameters:
+    * xy - {<OpenLayers.Geometry>} 
+    *
+    * Returns:
+    * {Boolean} Boolean value indicating whether the passed-in 
+    *          {<OpenLayers.Geometry>} object has the same  components as this
+    *          note that if ll passed in is null, returns false
+    *
+    */
+    equals:function(geom) {
+        var equals = false;
+        if (geom != null) {
+            equals = ((this.x == geom.x && this.y == geom.y) ||
+                      (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
+        }
+        return equals;
+    },
+    
+    /**
+     * Method: toShortString
+     *
+     * Returns:
+     * {String} Shortened String representation of Point object. 
+     *         (ex. <i>"5, 42"</i>)
+     */
+    toShortString: function() {
+        return (this.x + ", " + this.y);
+    },
+    
+    /**
+     * APIMethod: move
+     * Moves a point in place
+     *
+     * Parameters:
+     * x - {Float} 
+     * y - {Float} 
+     */
+    move: function(x, y) {
+        this.x = this.x + x;
+        this.y = this.y + y;
+        this.clearBounds();
+    },
+
+    /**
+     * APIMethod: rotate
+     * Rotate a point around another.
+     *
+     * Parameters:
+     * angle - {Float} Rotation angle in degrees (measured counterclockwise
+     *                 from the positive x-axis)
+     * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+     */
+    rotate: function(angle, origin) {
+        angle *= Math.PI / 180;
+        var radius = this.distanceTo(origin);
+        var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
+        this.x = origin.x + (radius * Math.cos(theta));
+        this.y = origin.y + (radius * Math.sin(theta));
+        this.clearBounds();
+    },
+
+    /**
+     * APIMethod: resize
+     * Resize a point relative to some origin.  For points, this has the effect
+     *     of scaling a vector (from the origin to the point).  This method is
+     *     more useful on geometry collection subclasses.
+     *
+     * Parameters:
+     * scale - {Float} Ratio of the new distance from the origin to the old
+     *                 distance from the origin.  A scale of 2 doubles the
+     *                 distance between the point and origin.
+     * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+     * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
+     */
+    resize: function(scale, origin, ratio) {
+        ratio = (ratio == undefined) ? 1 : ratio;
+        this.x = origin.x + (scale * ratio * (this.x - origin.x));
+        this.y = origin.y + (scale * (this.y - origin.y));
+        this.clearBounds();
+    },
+    
+    /**
+     * APIMethod: intersects
+     * Determine if the input geometry intersects this one.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+     *
+     * Returns:
+     * {Boolean} The input geometry intersects this one.
+     */
+    intersects: function(geometry) {
+        var intersect = false;
+        if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+            intersect = this.equals(geometry);
+        } else {
+            intersect = geometry.intersects(this);
+        }
+        return intersect;
+    },
+    
+    /**
+     * APIMethod: transform
+     * Translate the x,y properties of the point from source to dest.
+     * 
+     * Parameters:
+     * source - {<OpenLayers.Projection>} 
+     * dest - {<OpenLayers.Projection>}
+     * 
+     * Returns:
+     * {<OpenLayers.Geometry>} 
+     */
+    transform: function(source, dest) {
+        if ((source && dest)) {
+            OpenLayers.Projection.transform(
+                this, source, dest); 
+            this.bounds = null;
+        }       
+        return this;
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.Point"
+});
+/* ======================================================================
+    OpenLayers/Geometry/Rectangle.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Rectangle
+ * This class is *not supported*, and probably isn't what you're looking for.
+ *     Instead, most users probably want something like:
+ *     (code)
+ *     var poly = new OpenLayers.Bounds(0,0,10,10).toGeometry();
+ *     (end)
+ *     This will create a rectangular Polygon geometry. 
+ * 
+ * Inherits:
+ *  - <OpenLayers.Geometry>
+ */
+
+OpenLayers.Geometry.Rectangle = OpenLayers.Class(OpenLayers.Geometry, {
+
+    /** 
+     * Property: x
+     * {Float}
+     */
+    x: null,
+
+    /** 
+     * Property: y
+     * {Float}
+     */
+    y: null,
+
+    /** 
+     * Property: width
+     * {Float}
+     */
+    width: null,
+
+    /** 
+     * Property: height
+     * {Float}
+     */
+    height: null,
+
+    /**
+     * Constructor: OpenLayers.Geometry.Rectangle
+     * 
+     * Parameters:
+     * points - {Array(<OpenLayers.Geometry.Point>}
+     */
+    initialize: function(x, y, width, height) {
+        OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+        
+        this.x = x;
+        this.y = y;
+
+        this.width = width;
+        this.height = height;
+    },
+    
+    /**
+     * Method: calculateBounds
+     * Recalculate the bounds for the geometry.
+     */
+    calculateBounds: function() {
+        this.bounds = new OpenLayers.Bounds(this.x, this.y,
+                                            this.x + this.width, 
+                                            this.y + this.height);
+    },
+    
+    
+    /**
+     * APIMethod: getLength
+     * 
+     * Returns:
+     * {Float} The length of the geometry
+     */
+    getLength: function() {
+        var length = (2 * this.width) + (2 * this.height);
+        return length;
+    },
+
+    /**
+     * APIMethod: getArea
+     * 
+     * Returns:
+     * {Float} The area of the geometry
+     */
+    getArea: function() {
+        var area = this.width * this.height;
+        return area;
+    },    
+
+    CLASS_NAME: "OpenLayers.Geometry.Rectangle"
+});
+/* ======================================================================
+    OpenLayers/Geometry/Surface.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+OpenLayers.Geometry.Surface = OpenLayers.Class(OpenLayers.Geometry, {
+
+    initialize: function() {
+        OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.Surface"
+});
+/* ======================================================================
+    OpenLayers/Layer/MapServer/Untiled.js
+   ====================================================================== */
+
+/* Copyright 2006-2008 MetaCarta, Inc., published under the Clear BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the full text
+ * of the license. */
+
+ 
+/**
+ * @requires OpenLayers/Layer/MapServer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.MapServer.Untiled
+ * *Deprecated*.  To be removed in 3.0.  Instead use OpenLayers.Layer.MapServer
+ *     and pass the option 'singleTile' as true.
+ * 
+ * Inherits from: 
+ *  - <OpenLayers.Layer.MapServer>
+ */
+OpenLayers.Layer.MapServer.Untiled = OpenLayers.Class(OpenLayers.Layer.MapServer, {
+
+    /**
+     * APIProperty: singleTile
+     * {singleTile} Always true for untiled.
+     */
+    singleTile: true,
+
+    /**
+     * Constructor: OpenLayers.Layer.MapServer.Untiled
+     *
+     * Parameters:
+     * name - {String} 
+     * url - {String} 
+     * params - {Object} 
+     * options - {Object} 
+     */
+    initialize: function(name, url, params, options) {
+        OpenLayers.Layer.MapServer.prototype.initialize.apply(this, arguments);
+        
+        var msg = "The OpenLayers.Layer.MapServer.Untiled class is deprecated and " +
+                  "will be removed in 3.0. Instead, you should use the " +
+                  "normal OpenLayers.Layer.MapServer class, passing it the option " +
+                  "'singleTile' as true.";
+        OpenLayers.Console.warn(msg);
+    },    
+
+    /**
+     * Method: clone
+     * Create a clone of this layer
+     *
+     * Returns:
+     * {<OpenLayers.Layer.MapServer.Untiled>} An exact clone of this layer
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.MapServer.Untiled(this.name,
+                                                         this.url,
+                                                         this.params,
+                                                         this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.MapServer.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    }, 
+
+    CLASS_NAME: "OpenLayers.Layer.MapServer.Untiled"
+});
+/* ======================================================================
+    OpenLayers/Layer/Vector.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Renderer.js
+ * @requires OpenLayers/StyleMap.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Vector
+ * Instances of OpenLayers.Layer.Vector are used to render vector data from
+ *     a variety of sources. Create a new vector layer with the
+ *     <OpenLayers.Layer.Vector> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
+
+    /**
+     * Constant: EVENT_TYPES
+     * {Array(String)} Supported application event types.  Register a listener
+     *     for a particular event with the following syntax:
+     * (code)
+     * layer.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 layer.events.object.
+     *  - *element* {DOMElement} A reference to layer.events.element.
+     *
+     * Supported map event types (in addition to those from <OpenLayers.Layer>):
+     *  - *beforefeatureadded* Triggered before a feature is added.  Listeners
+     *      will receive an object with a *feature* property referencing the
+     *      feature to be added.
+     *  - *featureadded* Triggered after a feature is added.  The event
+     *      object passed to listeners will have a *feature* property with a
+     *      reference to the added feature.
+     *  - *featuresadded* Triggered after features are added.  The event
+     *      object passed to listeners will have a *features* property with a
+     *      reference to an array of added features.
+     *  - *beforefeatureremoved* Triggered before a feature is removed. Listeners
+     *      will receive an object with a *feature* property referencing the
+     *      feature to be removed.
+     *  - *featureremoved* Triggerd after a feature is removed. The event
+     *      object passed to listeners will have a *feature* property with a
+     *      reference to the removed feature.
+     *  - *featuresremoved* Triggered after features are removed. The event
+     *      object passed to listeners will have a *features* property with a
+     *      reference to an array of removed features.
+     *  - *featureselected* Triggered after a feature is selected.  Listeners
+     *      will receive an object with a *feature* property referencing the
+     *      selected feature.
+     *  - *featureunselected* Triggered after a feature is unselected.
+     *      Listeners will receive an object with a *feature* property
+     *      referencing the unselected feature.
+     *  - *beforefeaturemodified* Triggered when a feature is selected to 
+     *      be modified.  Listeners will receive an object with a *feature* 
+     *      property referencing the selected feature.
+     *  - *featuremodified* Triggered when a feature has been modified.
+     *      Listeners will receive an object with a *feature* property referencing 
+     *      the modified feature.
+     *  - *afterfeaturemodified* Triggered when a feature is finished being modified.
+     *      Listeners will receive an object with a *feature* property referencing 
+     *      the modified feature.
+     */
+    EVENT_TYPES: ["beforefeatureadded", "featureadded", "featuresadded",
+                  "beforefeatureremoved", "featureremoved", "featuresremoved",
+                  "beforefeatureselected", "featureselected", "featureunselected", 
+                  "beforefeaturemodified", "featuremodified", "afterfeaturemodified"],
+
+    /**
+     * APIProperty: isBaseLayer
+     * {Boolean} The layer is a base layer.  Default is true.  Set this property
+     * in the layer options
+     */
+    isBaseLayer: false,
+
+    /** 
+     * APIProperty: isFixed
+     * {Boolean} Whether the layer remains in one place while dragging the
+     * map.
+     */
+    isFixed: false,
+
+    /** 
+     * APIProperty: isVector
+     * {Boolean} Whether the layer is a vector layer.
+     */
+    isVector: true,
+
+    /** 
+     * APIProperty: features
+     * {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    features: null,
+    
+    /** 
+     * Property: selectedFeatures
+     * {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    selectedFeatures: null,
+    
+    /**
+     * Property: unrenderedFeatures
+     * {Object} hash of features, keyed by feature.id, that the renderer
+     *     failed to draw
+     */
+    unrenderedFeatures: null,
+
+    /**
+     * APIProperty: reportError
+     * {Boolean} report friendly error message when loading of renderer
+     * fails.
+     */
+    reportError: true, 
+
+    /** 
+     * APIProperty: style
+     * {Object} Default style for the layer
+     */
+    style: null,
+    
+    /**
+     * Property: styleMap
+     * {<OpenLayers.StyleMap>}
+     */
+    styleMap: null,
+    
+    /**
+     * Property: strategies
+     * {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer.
+     */
+    strategies: null,
+    
+    /**
+     * Property: protocol
+     * {<OpenLayers.Protocol>} Optional protocol for the layer.
+     */
+    protocol: null,
+    
+    /**
+     * Property: renderers
+     * {Array(String)} List of supported Renderer classes. Add to this list to
+     * add support for additional renderers. This list is ordered:
+     * the first renderer which returns true for the  'supported()'
+     * method will be used, if not defined in the 'renderer' option.
+     */
+    renderers: ['SVG', 'VML', 'Canvas'],
+    
+    /** 
+     * Property: renderer
+     * {<OpenLayers.Renderer>}
+     */
+    renderer: null,
+    
+    /**
+     * APIProperty: rendererOptions
+     * {Object} Options for the renderer. See {<OpenLayers.Renderer>} for
+     *     supported options.
+     */
+    rendererOptions: null,
+    
+    /** 
+     * APIProperty: geometryType
+     * {String} geometryType allows you to limit the types of geometries this
+     * layer supports. This should be set to something like
+     * "OpenLayers.Geometry.Point" to limit types.
+     */
+    geometryType: null,
+
+    /** 
+     * Property: drawn
+     * {Boolean} Whether the Vector Layer features have been drawn yet.
+     */
+    drawn: false,
+
+    /**
+     * Constructor: OpenLayers.Layer.Vector
+     * Create a new vector layer
+     *
+     * Parameters:
+     * name - {String} A name for the layer
+     * options - {Object} Optional object with non-default properties to set on
+     *           the layer.
+     *
+     * Returns:
+     * {<OpenLayers.Layer.Vector>} A new vector layer
+     */
+    initialize: function(name, options) {
+        
+        // concatenate events specific to vector with those from the base
+        this.EVENT_TYPES =
+            OpenLayers.Layer.Vector.prototype.EVENT_TYPES.concat(
+            OpenLayers.Layer.prototype.EVENT_TYPES
+        );
+
+        OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+
+        // allow user-set renderer, otherwise assign one
+        if (!this.renderer || !this.renderer.supported()) {  
+            this.assignRenderer();
+        }
+
+        // if no valid renderer found, display error
+        if (!this.renderer || !this.renderer.supported()) {
+            this.renderer = null;
+            this.displayError();
+        } 
+
+        if (!this.styleMap) {
+            this.styleMap = new OpenLayers.StyleMap();
+        }
+
+        this.features = [];
+        this.selectedFeatures = [];
+        this.unrenderedFeatures = {};
+        
+        // Allow for custom layer behavior
+        if(this.strategies){
+            for(var i=0, len=this.strategies.length; i<len; i++) {
+                this.strategies[i].setLayer(this);
+            }
+        }
+
+    },
+
+    /**
+     * APIMethod: destroy
+     * Destroy this layer
+     */
+    destroy: function() {
+        if (this.strategies) {
+            var strategy, i, len;
+            for(i=0, len=this.strategies.length; i<len; i++) {
+                strategy = this.strategies[i];
+                if(strategy.autoDestroy) {
+                    strategy.destroy();
+                }
+            }
+            this.strategies = null;
+        }
+        if (this.protocol) {
+            if(this.protocol.autoDestroy) {
+                this.protocol.destroy();
+            }
+            this.protocol = null;
+        }
+        this.destroyFeatures();
+        this.features = null;
+        this.selectedFeatures = null;
+        this.unrenderedFeatures = null;
+        if (this.renderer) {
+            this.renderer.destroy();
+        }
+        this.renderer = null;
+        this.geometryType = null;
+        this.drawn = null;
+        OpenLayers.Layer.prototype.destroy.apply(this, arguments);  
+    },
+
+    /** 
+     * Method: assignRenderer
+     * Iterates through the available renderer implementations and selects 
+     * and assigns the first one whose "supported()" function returns true.
+     */    
+    assignRenderer: function()  {
+        for (var i=0, len=this.renderers.length; i<this.renderers.length; i++) {
+            var rendererClass = OpenLayers.Renderer[this.renderers[i]];
+            if (rendererClass && rendererClass.prototype.supported()) {
+                this.renderer = new rendererClass(this.div,
+                    this.rendererOptions);
+                break;
+            }  
+        }  
+    },
+
+    /** 
+     * Method: displayError 
+     * Let the user know their browser isn't supported.
+     */
+    displayError: function() {
+        if (this.reportError) {
+            OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported", 
+                                     {'renderers':this.renderers.join("\n")}));
+        }    
+    },
+
+    /** 
+     * Method: setMap
+     * The layer has been added to the map. 
+     * 
+     * If there is no renderer set, the layer can't be used. Remove it.
+     * Otherwise, give the renderer a reference to the map and set its size.
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {        
+        OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+
+        if (!this.renderer) {
+            this.map.removeLayer(this);
+        } else {
+            this.renderer.map = this.map;
+            this.renderer.setSize(this.map.getSize());
+        }
+        if(this.strategies) {
+            var strategy, i, len;
+            for(i=0, len=this.strategies.length; i<len; i++) {
+                strategy = this.strategies[i];
+                if(strategy.autoActivate) {
+                    strategy.activate();
+                }
+            }
+        }
+    },
+
+    /**
+     * Method: removeMap
+     * The layer has been removed from the map.
+     *
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    removeMap: function(map) {
+        if(this.strategies) {
+            var strategy, i, len;
+            for(i=0, len=this.strategies.length; i<len; i++) {
+                strategy = this.strategies[i];
+                if(strategy.autoActivate) {
+                    strategy.deactivate();
+                }
+            }
+        }
+    },
+    
+    /**
+     * Method: onMapResize
+     * Notify the renderer of the change in size. 
+     * 
+     */
+    onMapResize: function() {
+        OpenLayers.Layer.prototype.onMapResize.apply(this, arguments);
+        this.renderer.setSize(this.map.getSize());
+    },
+
+    /**
+     * Method: moveTo
+     *  Reset the vector layer's div so that it once again is lined up with 
+     *   the map. Notify the renderer of the change of extent, and in the
+     *   case of a change of zoom level (resolution), have the 
+     *   renderer redraw features.
+     * 
+     *  If the layer has not yet been drawn, cycle through the layer's 
+     *   features and draw each one.
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     * zoomChanged - {Boolean} 
+     * dragging - {Boolean} 
+     */
+    moveTo: function(bounds, zoomChanged, dragging) {
+        OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+        
+        var coordSysUnchanged = true;
+
+        if (!dragging) {
+            this.renderer.root.style.visibility = "hidden";
+            
+            this.div.style.left = -parseInt(this.map.layerContainerDiv.style.left) + "px";
+            this.div.style.top = -parseInt(this.map.layerContainerDiv.style.top) + "px";
+            var extent = this.map.getExtent();
+            coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged);
+            
+            this.renderer.root.style.visibility = "visible";
+
+            // Force a reflow on gecko based browsers to prevent jump/flicker.
+            // This seems to happen on only certain configurations; it was originally
+            // noticed in FF 2.0 and Linux.
+            if (navigator.userAgent.toLowerCase().indexOf("gecko") != -1) {
+                this.div.scrollLeft = this.div.scrollLeft;
+            }
+            
+            if(!zoomChanged && coordSysUnchanged) {
+                var unrenderedFeatures = {};
+                for(var i in this.unrenderedFeatures) {
+                    var feature = this.unrenderedFeatures[i];
+                    if(!this.drawFeature(feature)) {
+                        unrenderedFeatures[i] = feature;
+                    }
+                }
+                this.unrenderedFeatures = unrenderedFeatures;
+            }
+        }
+        
+        if (!this.drawn || zoomChanged || !coordSysUnchanged) {
+            this.unrenderedFeatures = {};
+            this.drawn = true;
+            var feature;
+            for(var i=0, len=this.features.length; i<len; i++) {
+                if (i != (this.features.length - 1)) {
+                    this.renderer.locked = true;
+                } else {
+                    this.renderer.locked = false;
+                }    
+                feature = this.features[i];
+                if (!this.drawFeature(feature)) {
+                    this.unrenderedFeatures[feature.id] = feature;
+                };
+            }
+        }    
+    },
+
+    /**
+     * APIMethod: addFeatures
+     * Add Features to the layer.
+     *
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     * options - {Object}
+     */
+    addFeatures: function(features, options) {
+        if (!(features instanceof Array)) {
+            features = [features];
+        }
+        
+        var notify = !options || !options.silent;
+
+        for (var i=0, len=features.length; i<len; i++) {
+            if (i != (features.length - 1)) {
+                this.renderer.locked = true;
+            } else {
+                this.renderer.locked = false;
+            }    
+            var feature = features[i];
+            
+            if (this.geometryType &&
+              !(feature.geometry instanceof this.geometryType)) {
+                var throwStr = OpenLayers.i18n('componentShouldBe',
+                          {'geomType':this.geometryType.prototype.CLASS_NAME});
+                throw throwStr;
+              }
+
+            this.features.push(feature);
+            
+            //give feature reference to its layer
+            feature.layer = this;
+
+            if (!feature.style && this.style) {
+                feature.style = OpenLayers.Util.extend({}, this.style);
+            }
+
+            if (notify) {
+                this.events.triggerEvent("beforefeatureadded", {
+                    feature: feature
+                });
+                this.preFeatureInsert(feature);
+            }
+
+            if (this.drawn) {
+                if(!this.drawFeature(feature)) {
+                    this.unrenderedFeatures[feature.id] = feature;
+                }
+            }
+            
+            if (notify) {
+                this.events.triggerEvent("featureadded", {
+                    feature: feature
+                });
+                this.onFeatureInsert(feature);
+            }
+        }
+        
+        if(notify) {
+            this.events.triggerEvent("featuresadded", {features: features});
+        }
+    },
+
+
+    /**
+     * APIMethod: removeFeatures
+     * 
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     * options - {Object}
+     */
+    removeFeatures: function(features, options) {
+        if (!(features instanceof Array)) {
+            features = [features];
+        }
+        if (features.length <= 0) {
+            return;
+        }
+
+        var notify = !options || !options.silent;
+
+        for (var i = features.length - 1; i >= 0; i--) {
+            // We remain locked so long as we're not at 0
+            // and the 'next' feature has a geometry. We do the geometry check
+            // because if all the features after the current one are 'null', we
+            // won't call eraseGeometry, so we break the 'renderer functions
+            // will always be called with locked=false *last*' rule. The end result
+            // is a possible gratiutious unlocking to save a loop through the rest 
+            // of the list checking the remaining features every time. So long as
+            // null geoms are rare, this is probably okay.    
+            if (i != 0 && features[i-1].geometry) {
+                this.renderer.locked = true;
+            } else {
+                this.renderer.locked = false;
+            }
+    
+            var feature = features[i];
+            delete this.unrenderedFeatures[feature.id];
+
+            if (notify) {
+                this.events.triggerEvent("beforefeatureremoved", {
+                    feature: feature
+                });
+            }
+
+            this.features = OpenLayers.Util.removeItem(this.features, feature);
+            // feature has no layer at this point
+            feature.layer = null;
+
+            if (feature.geometry) {
+                this.renderer.eraseGeometry(feature.geometry);
+            }
+                    
+            //in the case that this feature is one of the selected features, 
+            // remove it from that array as well.
+            if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){
+                OpenLayers.Util.removeItem(this.selectedFeatures, feature);
+            }
+
+            if (notify) {
+                this.events.triggerEvent("featureremoved", {
+                    feature: feature
+                });
+            }
+        }
+
+        if (notify) {
+            this.events.triggerEvent("featuresremoved", {features: features});
+        }
+    },
+
+    /**
+     * APIMethod: destroyFeatures
+     * Erase and destroy features on the layer.
+     *
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} An optional array of
+     *     features to destroy.  If not supplied, all features on the layer
+     *     will be destroyed.
+     * options - {Object}
+     */
+    destroyFeatures: function(features, options) {
+        var all = (features == undefined); // evaluates to true if
+                                           // features is null
+        if(all) {
+            features = this.features;
+        }
+        this.removeFeatures(features, options);
+        for (var i = 0; i < features.length; i++) {
+            features[i].destroy();
+        }
+    },
+
+    /**
+     * APIMethod: drawFeature
+     * Draw (or redraw) a feature on the layer.  If the optional style argument
+     * is included, this style will be used.  If no style is included, the
+     * feature's style will be used.  If the feature doesn't have a style,
+     * the layer's style will be used.
+     * 
+     * Parameters: 
+     * feature - {<OpenLayers.Feature.Vector>} 
+     * style - {Object} Symbolizer hash or {String} renderIntent
+     * 
+     * Returns:
+     * {Boolean} true if the renderer was able to draw the feature, false
+     *     otherwise
+     */
+    drawFeature: function(feature, style) {
+        if (typeof style != "object") {
+            var renderIntent = typeof style == "string" ?
+                style : feature.renderIntent;
+            style = feature.style || this.style;
+            if (!style) {
+                style = this.styleMap.createSymbolizer(feature, renderIntent);
+            }
+        }
+        
+        return this.renderer.drawFeature(feature, style);
+    },
+    
+    /**
+     * Method: eraseFeatures
+     * Erase features from the layer.
+     *
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    eraseFeatures: function(features) {
+        this.renderer.eraseFeatures(features);
+    },
+
+    /**
+     * Method: getFeatureFromEvent
+     * Given an event, return a feature if the event occurred over one.
+     * Otherwise, return null.
+     *
+     * Parameters:
+     * evt - {Event} 
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A feature if one was under the event.
+     */
+    getFeatureFromEvent: function(evt) {
+        if (!this.renderer) {
+            OpenLayers.Console.error(OpenLayers.i18n("getFeatureError")); 
+            return null;
+        }    
+        var featureId = this.renderer.getFeatureIdFromEvent(evt);
+        return this.getFeatureById(featureId);
+    },
+    
+    /**
+     * APIMethod: getFeatureById
+     * Given a feature id, return the feature if it exists in the features array
+     *
+     * Parameters:
+     * featureId - {String} 
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+     * featureId
+     */
+    getFeatureById: function(featureId) {
+        //TBD - would it be more efficient to use a hash for this.features?
+        var feature = null;
+        for(var i=0, len=this.features.length; i<len; ++i) {
+            if(this.features[i].id == featureId) {
+                feature = this.features[i];
+                break;
+            }
+        }
+        return feature;
+    },
+    
+    /**
+     * Unselect the selected features
+     * i.e. clears the featureSelection array
+     * change the style back
+    clearSelection: function() {
+
+       var vectorLayer = this.map.vectorLayer;
+        for (var i = 0; i < this.map.featureSelection.length; i++) {
+            var featureSelection = this.map.featureSelection[i];
+            vectorLayer.drawFeature(featureSelection, vectorLayer.style);
+        }
+        this.map.featureSelection = [];
+    },
+     */
+
+
+    /**
+     * APIMethod: onFeatureInsert
+     * method called after a feature is inserted.
+     * Does nothing by default. Override this if you
+     * need to do something on feature updates.
+     *
+     * Paarameters: 
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    onFeatureInsert: function(feature) {
+    },
+    
+    /**
+     * APIMethod: preFeatureInsert
+     * method called before a feature is inserted.
+     * Does nothing by default. Override this if you
+     * need to do something when features are first added to the
+     * layer, but before they are drawn, such as adjust the style.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    preFeatureInsert: function(feature) {
+    },
+
+    /** 
+     * APIMethod: getDataExtent
+     * Calculates the max extent which includes all of the features.
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>}
+     */
+    getDataExtent: function () {
+        var maxExtent = null;
+        if( this.features && (this.features.length > 0)){
+            var maxExtent = this.features[0].geometry.getBounds();
+            for(var i=0, len=this.features.length; i<len; i++){
+                maxExtent.extend(this.features[i].geometry.getBounds());
+            }
+        }
+
+        return maxExtent;
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Vector"
+});
+/* ======================================================================
+    OpenLayers/Layer/WMS/Untiled.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+ 
+/**
+ * @requires OpenLayers/Layer/WMS.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WMS.Untiled
+ * *Deprecated*.  To be removed in 3.0.  Instead use OpenLayers.Layer.WMS and 
+ *     pass the option 'singleTile' as true.
+ * 
+ * Inherits from: 
+ *  - <OpenLayers.Layer.WMS>
+ */
+OpenLayers.Layer.WMS.Untiled = OpenLayers.Class(OpenLayers.Layer.WMS, {
+
+    /**
+     * APIProperty: singleTile
+     * {singleTile} Always true for untiled.
+     */
+    singleTile: true,
+
+    /**
+     * Constructor: OpenLayers.Layer.WMS.Untiled
+     *
+     * Parameters:
+     * name - {String} 
+     * url - {String} 
+     * params - {Object} 
+     * options - {Object} 
+     */
+    initialize: function(name, url, params, options) {
+        OpenLayers.Layer.WMS.prototype.initialize.apply(this, arguments);
+        
+        var msg = "The OpenLayers.Layer.WMS.Untiled class is deprecated and " +
+                  "will be removed in 3.0. Instead, you should use the " +
+                  "normal OpenLayers.Layer.WMS class, passing it the option " +
+                  "'singleTile' as true.";
+        OpenLayers.Console.warn(msg);
+    },    
+
+    /**
+     * Method: clone
+     * Create a clone of this layer
+     *
+     * Returns:
+     * {<OpenLayers.Layer.WMS.Untiled>} An exact clone of this layer
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.WMS.Untiled(this.name,
+                                                   this.url,
+                                                   this.params,
+                                                   this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.WMS.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    }, 
+
+    CLASS_NAME: "OpenLayers.Layer.WMS.Untiled"
+});
+/* ======================================================================
+    OpenLayers/Format/Filter.js
+   ====================================================================== */
+
+/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter
+ * Read/Wite ogc:Filter. Create a new instance with the <OpenLayers.Format.Filter>
+ *     constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * APIProperty: defaultVersion
+     * {String} Version number to assume if none found.  Default is "1.0.0".
+     */
+    defaultVersion: "1.0.0",
+    
+    /**
+     * APIProperty: version
+     * {String} Specify a version string if one is known.
+     */
+    version: null,
+    
+    /**
+     * Property: parser
+     * {Object} Instance of the versioned parser.  Cached for multiple read and
+     *     write calls of the same version.
+     */
+    parser: null,
+
+    /**
+     * Constructor: OpenLayers.Format.Filter
+     * Create a new parser for Filter.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: write
+     * Write an ogc:Filter given a filter object.
+     *
+     * Parameters:
+     * filter - {<OpenLayers.Filter>} An filter.
+     * options - {Object} Optional configuration object.
+     *
+     * Returns:
+     * {Elment} An ogc:Filter element node.
+     */
+    write: function(filter, options) {
+        var version = (options && options.version) ||
+                      this.version || this.defaultVersion;
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.Filter[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a Filter parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        return this.parser.write(filter);
+        //return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+    },
+    
+    /**
+     * APIMethod: read
+     * Read and Filter doc and return an object representing the Filter.
+     *
+     * Parameters:
+     * data - {String | DOMElement} Data to read.
+     *
+     * Returns:
+     * {<OpenLayers.Filter>} A filter object.
+     */
+    read: function(data) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var root = data.nodeType == 9 ? data.documentElement : data;
+        var version = this.version;
+        if(!version) {
+            version = this.defaultVersion;
+        }
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.Filter[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a Filter parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        var filter = this.parser.read(data);
+        return filter;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.Filter" 
+});
+/* ======================================================================
+    OpenLayers/Format/SLD.js
+   ====================================================================== */
+
+/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Style.js
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD
+ * Read/Wite SLD. Create a new instance with the <OpenLayers.Format.SLD>
+ *     constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * APIProperty: defaultVersion
+     * {String} Version number to assume if none found.  Default is "1.0.0".
+     */
+    defaultVersion: "1.0.0",
+    
+    /**
+     * APIProperty: version
+     * {String} Specify a version string if one is known.
+     */
+    version: null,
+    
+    /**
+     * Property: parser
+     * {Object} Instance of the versioned parser.  Cached for multiple read and
+     *     write calls of the same version.
+     */
+    parser: null,
+
+    /**
+     * Constructor: OpenLayers.Format.SLD
+     * Create a new parser for SLD.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: write
+     * Write a SLD document given a list of styles.
+     *
+     * Parameters:
+     * sld - {Object} An object representing the SLD.
+     * options - {Object} Optional configuration object.
+     *
+     * Returns:
+     * {String} An SLD document string.
+     */
+    write: function(sld, options) {
+        var version = (options && options.version) ||
+                      this.version || this.defaultVersion;
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.SLD[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a SLD parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        var root = this.parser.write(sld);
+        return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+    },
+    
+    /**
+     * APIMethod: read
+     * Read and SLD doc and return an object representing the SLD.
+     *
+     * Parameters:
+     * data - {String | DOMElement} Data to read.
+     *
+     * Returns:
+     * {Object} An object representing the SLD.
+     */
+    read: function(data) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var root = data.documentElement;
+        var version = this.version;
+        if(!version) {
+            version = root.getAttribute("version");
+            if(!version) {
+                version = this.defaultVersion;
+            }
+        }
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.SLD[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a SLD parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        var sld = this.parser.read(data);
+        return sld;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.SLD" 
+});
+/* ======================================================================
+    OpenLayers/Format/Text.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Text
+ * Read Text format. Create a new instance with the <OpenLayers.Format.Text>
+ *     constructor. This reads text which is formatted like CSV text, using
+ *     tabs as the seperator by default. It provides parsing of data originally
+ *     used in the MapViewerService, described on the wiki. This Format is used
+ *     by the <OpenLayers.Layer.Text> class.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.Text = OpenLayers.Class(OpenLayers.Format, {
+    
+    /**
+     * Constructor: OpenLayers.Format.Text
+     * Create a new parser for TSV Text.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.prototype.initialize.apply(this, [options]);
+    }, 
+
+    /**
+     * APIMethod: read
+     * Return a list of features from a Tab Seperated Values text string.
+     * 
+     * Parameters:
+     * data - {String} 
+     *
+     * Returns:
+     * An Array of <OpenLayers.Feature.Vector>s
+     */
+    read: function(text) {
+        var lines = text.split('\n');
+        var columns;
+        var features = [];
+        // length - 1 to allow for trailing new line
+        for (var lcv = 0; lcv < (lines.length - 1); lcv++) {
+            var currLine = lines[lcv].replace(/^\s*/,'').replace(/\s*$/,'');
+        
+            if (currLine.charAt(0) != '#') { /* not a comment */
+            
+                if (!columns) {
+                    //First line is columns
+                    columns = currLine.split('\t');
+                } else {
+                    var vals = currLine.split('\t');
+                    var geometry = new OpenLayers.Geometry.Point(0,0);
+                    var attributes = {};
+                    var style = {};
+                    var icon, iconSize, iconOffset, overflow;
+                    var set = false;
+                    for (var valIndex = 0; valIndex < vals.length; valIndex++) {
+                        if (vals[valIndex]) {
+                            if (columns[valIndex] == 'point') {
+                                var coords = vals[valIndex].split(',');
+                                geometry.y = parseFloat(coords[0]);
+                                geometry.x = parseFloat(coords[1]);
+                                set = true;
+                            } else if (columns[valIndex] == 'lat') {
+                                geometry.y = parseFloat(vals[valIndex]);
+                                set = true;
+                            } else if (columns[valIndex] == 'lon') {
+                                geometry.x = parseFloat(vals[valIndex]);
+                                set = true;
+                            } else if (columns[valIndex] == 'title')
+                                attributes['title'] = vals[valIndex];
+                            else if (columns[valIndex] == 'image' ||
+                                     columns[valIndex] == 'icon')
+                                style['externalGraphic'] = vals[valIndex];
+                            else if (columns[valIndex] == 'iconSize') {
+                                var size = vals[valIndex].split(',');
+                                style['graphicWidth'] = parseFloat(size[0]);
+                                style['graphicHeight'] = parseFloat(size[1]);
+                            } else if (columns[valIndex] == 'iconOffset') {
+                                var offset = vals[valIndex].split(',');
+                                style['graphicXOffset'] = parseFloat(offset[0]);
+                                style['graphicYOffset'] = parseFloat(offset[1]);
+                            } else if (columns[valIndex] == 'description') {
+                                attributes['description'] = vals[valIndex];
+                            } else if (columns[valIndex] == 'overflow') {
+                                attributes['overflow'] = vals[valIndex];
+                            }    
+                        }
+                    }
+                    if (set) {
+                      if (this.internalProjection && this.externalProjection) {
+                          geometry.transform(this.externalProjection, 
+                                             this.internalProjection); 
+                      }                       
+                      var feature = new OpenLayers.Feature.Vector(geometry, attributes, style);
+                      features.push(feature);
+                    }
+                }
+            }
+        }
+        return features;
+    },   
+
+    CLASS_NAME: "OpenLayers.Format.Text" 
+});    
+/* ======================================================================
+    OpenLayers/Geometry/MultiLineString.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiLineString
+ * A MultiLineString is a geometry with multiple <OpenLayers.Geometry.LineString>
+ * components.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Geometry.Collection>
+ *  - <OpenLayers.Geometry> 
+ */
+OpenLayers.Geometry.MultiLineString = OpenLayers.Class(
+  OpenLayers.Geometry.Collection, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of
+     * components that the collection can include.  A null value means the
+     * component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.LineString"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.MultiLineString
+     * Constructor for a MultiLineString Geometry.
+     *
+     * Parameters: 
+     * components - {Array(<OpenLayers.Geometry.LineString>)} 
+     *
+     */
+    initialize: function(components) {
+        OpenLayers.Geometry.Collection.prototype.initialize.apply(this, 
+                                                                  arguments);        
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
+});
+/* ======================================================================
+    OpenLayers/Geometry/MultiPoint.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPoint
+ * MultiPoint is a collection of Points.  Create a new instance with the
+ * <OpenLayers.Geometry.MultiPoint> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Geometry.Collection>
+ *  - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.MultiPoint = OpenLayers.Class(
+  OpenLayers.Geometry.Collection, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of
+     * components that the collection can include.  A null value means the
+     * component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.Point"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.MultiPoint
+     * Create a new MultiPoint Geometry
+     *
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry.Point>)} 
+     *
+     * Returns:
+     * {<OpenLayers.Geometry.MultiPoint>}
+     */
+    initialize: function(components) {
+        OpenLayers.Geometry.Collection.prototype.initialize.apply(this, 
+                                                                  arguments);
+    },
+
+    /**
+     * APIMethod: addPoint
+     * Wrapper for <OpenLayers.Geometry.Collection.addComponent>
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>} Point to be added
+     * index - {Integer} Optional index
+     */
+    addPoint: function(point, index) {
+        this.addComponent(point, index);
+    },
+    
+    /**
+     * APIMethod: removePoint
+     * Wrapper for <OpenLayers.Geometry.Collection.removeComponent>
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>} Point to be removed
+     */
+    removePoint: function(point){
+        this.removeComponent(point);
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.MultiPoint"
+});
+/* ======================================================================
+    OpenLayers/Geometry/MultiPolygon.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPolygon
+ * MultiPolygon is a geometry with multiple <OpenLayers.Geometry.Polygon>
+ * components.  Create a new instance with the <OpenLayers.Geometry.MultiPolygon>
+ * constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Geometry.Collection>
+ */
+OpenLayers.Geometry.MultiPolygon = OpenLayers.Class(
+  OpenLayers.Geometry.Collection, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of
+     * components that the collection can include.  A null value means the
+     * component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.Polygon"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.MultiPolygon
+     * Create a new MultiPolygon geometry
+     *
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry.Polygon>)} An array of polygons
+     *              used to generate the MultiPolygon
+     *
+     */
+    initialize: function(components) {
+        OpenLayers.Geometry.Collection.prototype.initialize.apply(this, 
+                                                                  arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.MultiPolygon"
+});
+/* ======================================================================
+    OpenLayers/Geometry/Polygon.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Polygon 
+ * Polygon is a collection of Geometry.LinearRings. 
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Geometry.Collection> 
+ *  - <OpenLayers.Geometry> 
+ */
+OpenLayers.Geometry.Polygon = OpenLayers.Class(
+  OpenLayers.Geometry.Collection, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of
+     * components that the collection can include.  A null value means the
+     * component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.LinearRing"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.Polygon
+     * Constructor for a Polygon geometry. 
+     * The first ring (this.component[0])is the outer bounds of the polygon and 
+     * all subsequent rings (this.component[1-n]) are internal holes.
+     *
+     *
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry.LinearRing>)} 
+     */
+    initialize: function(components) {
+        OpenLayers.Geometry.Collection.prototype.initialize.apply(this, 
+                                                                  arguments);
+    },
+    
+    /** 
+     * APIMethod: getArea
+     * Calculated by subtracting the areas of the internal holes from the 
+     *   area of the outer hole.
+     * 
+     * Returns:
+     * {float} The area of the geometry
+     */
+    getArea: function() {
+        var area = 0.0;
+        if ( this.components && (this.components.length > 0)) {
+            area += Math.abs(this.components[0].getArea());
+            for (var i=1, len=this.components.length; i<len; i++) {
+                area -= Math.abs(this.components[i].getArea());
+            }
+        }
+        return area;
+    },
+
+    /**
+     * Method: containsPoint
+     * Test if a point is inside a polygon.  Points on a polygon edge are
+     *     considered inside.
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>}
+     *
+     * Returns:
+     * {Boolean | Number} The point is inside the polygon.  Returns 1 if the
+     *     point is on an edge.  Returns boolean otherwise.
+     */
+    containsPoint: function(point) {
+        var numRings = this.components.length;
+        var contained = false;
+        if(numRings > 0) {
+            // check exterior ring - 1 means on edge, boolean otherwise
+            contained = this.components[0].containsPoint(point);
+            if(contained !== 1) {
+                if(contained && numRings > 1) {
+                    // check interior rings
+                    var hole;
+                    for(var i=1; i<numRings; ++i) {
+                        hole = this.components[i].containsPoint(point);
+                        if(hole) {
+                            if(hole === 1) {
+                                // on edge
+                                contained = 1;
+                            } else {
+                                // in hole
+                                contained = false;
+                            }                            
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return contained;
+    },
+
+    /**
+     * APIMethod: intersects
+     * Determine if the input geometry intersects this one.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+     *
+     * Returns:
+     * {Boolean} The input geometry intersects this one.
+     */
+    intersects: function(geometry) {
+        var intersect = false;
+        var i, len;
+        if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+            intersect = this.containsPoint(geometry);
+        } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" ||
+                  geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+            // check if rings/linestrings intersect
+            for(i=0, len=this.components.length; i<len; ++i) {
+                intersect = geometry.intersects(this.components[i]);
+                if(intersect) {
+                    break;
+                }
+            }
+            if(!intersect) {
+                // check if this poly contains points of the ring/linestring
+                for(i=0, len=geometry.components.length; i<len; ++i) {
+                    intersect = this.containsPoint(geometry.components[i]);
+                    if(intersect) {
+                        break;
+                    }
+                }
+            }
+        } else {
+            for(i=0, len=geometry.components.length; i<len; ++ i) {
+                intersect = this.intersects(geometry.components[i]);
+                if(intersect) {
+                    break;
+                }
+            }
+        }
+        // check case where this poly is wholly contained by another
+        if(!intersect && geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+            // exterior ring points will be contained in the other geometry
+            var ring = this.components[0];
+            for(i=0, len=ring.components.length; i<len; ++i) {
+                intersect = geometry.containsPoint(ring.components[i]);
+                if(intersect) {
+                    break;
+                }
+            }
+        }
+        return intersect;
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.Polygon"
+});
+
+/**
+ * APIMethod: createRegularPolygon
+ * Create a regular polygon around a radius. Useful for creating circles 
+ * and the like.
+ *
+ * Parameters:
+ * origin - {<OpenLayers.Geometry.Point>} center of polygon.
+ * radius - {Float} distance to vertex, in map units.
+ * sides - {Integer} Number of sides. 20 approximates a circle.
+ * rotation - {Float} original angle of rotation, in degrees.
+ */
+OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) {  
+    var angle = Math.PI * ((1/sides) - (1/2));
+    if(rotation) {
+        angle += (rotation / 180) * Math.PI;
+    }
+    var rotatedAngle, x, y;
+    var points = [];
+    for(var i=0; i<sides; ++i) {
+        rotatedAngle = angle + (i * 2 * Math.PI / sides);
+        x = origin.x + (radius * Math.cos(rotatedAngle));
+        y = origin.y + (radius * Math.sin(rotatedAngle));
+        points.push(new OpenLayers.Geometry.Point(x, y));
+    }
+    var ring = new OpenLayers.Geometry.LinearRing(points);
+    return new OpenLayers.Geometry.Polygon([ring]);
+};
+/* ======================================================================
+    OpenLayers/Handler/Point.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Point
+ * Handler to draw a point on the map.  Point is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.  The handler triggers
+ * callbacks for 'done' and 'cancel'.  Create a new instance with the
+ * <OpenLayers.Handler.Point> constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, {
+    
+    /**
+     * Property: point
+     * {<OpenLayers.Feature.Vector>} The currently drawn point
+     */
+    point: null,
+
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>} The temporary drawing layer
+     */
+    layer: null,
+    
+    /**
+     * Property: drawing 
+     * {Boolean} A point is being drawn
+     */
+    drawing: false,
+    
+    /**
+     * Property: mouseDown
+     * {Boolean} The mouse is down
+     */
+    mouseDown: false,
+
+    /**
+     * Property: lastDown
+     * {<OpenLayers.Pixel>} Location of the last mouse down
+     */
+    lastDown: null,
+
+    /**
+     * Property: lastUp
+     * {<OpenLayers.Pixel>}
+     */
+    lastUp: null,
+
+    /**
+     * Constructor: OpenLayers.Handler.Point
+     * Create a new point handler.
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} The control that owns this handler
+     * callbacks - {Object} An object with a 'done' property whose value is a
+     *             function to be called when the point drawing is finished.
+     *             The callback should expect to recieve a single argument,
+     *             the point geometry.  If the callbacks object contains a
+     *             'cancel' property, this function will be called when the
+     *             handler is deactivated while drawing.  The cancel should
+     *             expect to receive a geometry.
+     * options - {Object} An optional object with properties to be set on the
+     *           handler
+     */
+    initialize: function(control, callbacks, options) {
+        // TBD: deal with style
+        this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
+
+        OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+    },
+    
+    /**
+     * APIMethod: activate
+     * turn on the handler
+     */
+    activate: function() {
+        if(!OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+            return false;
+        }
+        // create temporary vector layer for rendering geometry sketch
+        // TBD: this could be moved to initialize/destroy - setting visibility here
+        var options = {
+            displayInLayerSwitcher: false,
+            // indicate that the temp vector layer will never be out of range
+            // without this, resolution properties must be specified at the
+            // map-level for this temporary layer to init its resolutions
+            // correctly
+            calculateInRange: function() { return true; }
+        };
+        this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
+        this.map.addLayer(this.layer);
+        return true;
+    },
+    
+    /**
+     * Method: createFeature
+     * Add temporary features
+     */
+    createFeature: function() {
+        this.point = new OpenLayers.Feature.Vector(
+                                              new OpenLayers.Geometry.Point());
+    },
+
+    /**
+     * APIMethod: deactivate
+     * turn off the handler
+     */
+    deactivate: function() {
+        if(!OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+            return false;
+        }
+        // call the cancel callback if mid-drawing
+        if(this.drawing) {
+            this.cancel();
+        }
+        // If a layer's map property is set to null, it means that that layer
+        // isn't added to the map. Since we ourself added the layer to the map
+        // in activate(), we can assume that if this.layer.map is null it means
+        // that the layer has been destroyed (as a result of map.destroy() for
+        // example.
+        if (this.layer.map != null) {
+            this.layer.destroy(false);
+        }
+        this.layer = null;
+        return true;
+    },
+    
+    /**
+     * Method: destroyFeature
+     * Destroy the temporary geometries
+     */
+    destroyFeature: function() {
+        if(this.point) {
+            this.point.destroy();
+        }
+        this.point = null;
+    },
+
+    /**
+     * Method: finalize
+     * Finish the geometry and call the "done" callback.
+     */
+    finalize: function() {
+        this.layer.renderer.clear();
+        this.drawing = false;
+        this.mouseDown = false;
+        this.lastDown = null;
+        this.lastUp = null;
+        this.callback("done", [this.geometryClone()]);
+        this.destroyFeature();
+    },
+
+    /**
+     * APIMethod: cancel
+     * Finish the geometry and call the "cancel" callback.
+     */
+    cancel: function() {
+        this.layer.renderer.clear();
+        this.drawing = false;
+        this.mouseDown = false;
+        this.lastDown = null;
+        this.lastUp = null;
+        this.callback("cancel", [this.geometryClone()]);
+        this.destroyFeature();
+    },
+
+    /**
+     * Method: click
+     * Handle clicks.  Clicks are stopped from propagating to other listeners
+     *     on map.events or other dom elements.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    click: function(evt) {
+        OpenLayers.Event.stop(evt);
+        return false;
+    },
+
+    /**
+     * Method: dblclick
+     * Handle double-clicks.  Double-clicks are stopped from propagating to other
+     *     listeners on map.events or other dom elements.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    dblclick: function(evt) {
+        OpenLayers.Event.stop(evt);
+        return false;
+    },
+    
+    /**
+     * Method: drawFeature
+     * Render features on the temporary layer.
+     */
+    drawFeature: function() {
+        this.layer.drawFeature(this.point, this.style);
+    },
+    
+    /**
+     * Method: geometryClone
+     * Return a clone of the relevant geometry.
+     *
+     * Returns:
+     * {<OpenLayers.Geometry.Point>}
+     */
+    geometryClone: function() {
+        return this.point.geometry.clone();
+    },
+  
+    /**
+     * Method: mousedown
+     * Handle mouse down.  Adjust the geometry and redraw.
+     * Return determines whether to propagate the event on the map.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mousedown: function(evt) {
+        // check keyboard modifiers
+        if(!this.checkModifiers(evt)) {
+            return true;
+        }
+        // ignore double-clicks
+        if(this.lastDown && this.lastDown.equals(evt.xy)) {
+            return true;
+        }
+        if(this.lastDown == null) {
+            this.createFeature();
+        }
+        this.lastDown = evt.xy;
+        this.drawing = true;
+        var lonlat = this.map.getLonLatFromPixel(evt.xy);
+        this.point.geometry.x = lonlat.lon;
+        this.point.geometry.y = lonlat.lat;
+        this.drawFeature();
+        return false;
+    },
+
+    /**
+     * Method: mousemove
+     * Handle mouse move.  Adjust the geometry and redraw.
+     * Return determines whether to propagate the event on the map.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mousemove: function (evt) {
+        if(this.drawing) {
+            var lonlat = this.map.getLonLatFromPixel(evt.xy);
+            this.point.geometry.x = lonlat.lon;
+            this.point.geometry.y = lonlat.lat;
+            this.point.geometry.clearBounds();
+            this.drawFeature();
+        }
+        return true;
+    },
+
+    /**
+     * Method: mouseup
+     * Handle mouse up.  Send the latest point in the geometry to the control.
+     * Return determines whether to propagate the event on the map.
+     *
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mouseup: function (evt) {
+        if(this.drawing) {
+            this.finalize();
+            return false;
+        } else {
+            return true;
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Point"
+});
+/* ======================================================================
+    OpenLayers/Layer/GML.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.GML
+ * Create a vector layer by parsing a GML file. The GML file is
+ *     passed in as a parameter.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.GML = OpenLayers.Class(OpenLayers.Layer.Vector, {
+    
+    /**
+      * Property: loaded
+      * {Boolean} Flag for whether the GML data has been loaded yet.
+      */
+    loaded: false,
+
+    /**
+      * APIProperty: format
+      * {<OpenLayers.Format>} The format you want the data to be parsed with.
+      */
+    format: null,
+
+    /**
+     * APIProperty: formatOptions
+     * {Object} Hash of options which should be passed to the format when it is
+     * created. Must be passed in the constructor.
+     */
+    formatOptions: null, 
+    
+    /**
+     * Constructor: OpenLayers.Layer.GML
+     * Load and parse a single file on the web, according to the format
+     * provided via the 'format' option, defaulting to GML. 
+     *
+     * Parameters:
+     * name - {String} 
+     * url - {String} URL of a GML file.
+     * options - {Object} Hashtable of extra options to tag onto the layer.
+     */
+     initialize: function(name, url, options) {
+        var newArguments = [];
+        newArguments.push(name, options);
+        OpenLayers.Layer.Vector.prototype.initialize.apply(this, newArguments);
+        this.url = url;
+    },
+
+    /**
+     * APIMethod: setVisibility
+     * Set the visibility flag for the layer and hide/show&redraw accordingly. 
+     * Fire event unless otherwise specified
+     * GML will be loaded if the layer is being made visible for the first
+     * time.
+     *  
+     * Parameters:
+     * visible - {Boolean} Whether or not to display the layer 
+     *                          (if in range)
+     * noEvent - {Boolean} 
+     */
+    setVisibility: function(visibility, noEvent) {
+        OpenLayers.Layer.Vector.prototype.setVisibility.apply(this, arguments);
+        if(this.visibility && !this.loaded){
+            // Load the GML
+            this.loadGML();
+        }
+    },
+
+    /**
+     * Method: moveTo
+     * If layer is visible and GML has not been loaded, load GML, then load GML
+     * and call OpenLayers.Layer.Vector.moveTo() to redraw at the new location.
+     * 
+     * Parameters:
+     * bounds - {Object} 
+     * zoomChanged - {Object} 
+     * minor - {Object} 
+     */
+    moveTo:function(bounds, zoomChanged, minor) {
+        OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments);
+        // Wait until initialisation is complete before loading GML
+        // otherwise we can get a race condition where the root HTML DOM is
+        // loaded after the GML is paited.
+        // See http://trac.openlayers.org/ticket/404
+        if(this.visibility && !this.loaded){
+            this.events.triggerEvent("loadstart");
+            this.loadGML();
+        }
+    },
+
+    /**
+     * Method: loadGML
+     */
+    loadGML: function() {
+        if (!this.loaded) {
+            OpenLayers.Request.GET({
+                url: this.url,
+                success: this.requestSuccess,
+                failure: this.requestFailure,
+                scope: this
+            });
+            this.loaded = true;
+        }    
+    },    
+    
+    /**
+     * Method: setUrl
+     * Change the URL and reload the GML
+     *
+     * Parameters:
+     * url - {String} URL of a GML file.
+     */
+    setUrl:function(url) {
+        this.url = url;
+        this.destroyFeatures();
+        this.loaded = false;
+        this.events.triggerEvent("loadstart");
+        this.loadGML();
+    },
+    
+    /**
+     * Method: requestSuccess
+     * Process GML after it has been loaded.
+     * Called by initialise() and loadUrl() after the GML has been loaded.
+     *
+     * Parameters:
+     * request - {String} 
+     */
+    requestSuccess:function(request) {
+        var doc = request.responseXML;
+        
+        if (!doc || !doc.documentElement) {
+            doc = request.responseText;
+        }
+        
+        var options = {};
+        
+        OpenLayers.Util.extend(options, this.formatOptions);
+        if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+            options.externalProjection = this.projection;
+            options.internalProjection = this.map.getProjectionObject();
+        }    
+        
+        var gml = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
+        this.addFeatures(gml.read(doc));
+        this.events.triggerEvent("loadend");
+    },
+    
+    /**
+     * Method: requestFailure
+     * Process a failed loading of GML.
+     * Called by initialise() and loadUrl() if there was a problem loading GML.
+     *
+     * Parameters:
+     * request - {String} 
+     */
+    requestFailure: function(request) {
+        OpenLayers.Console.userError(OpenLayers.i18n("errorLoadingGML", {'url':this.url}));
+        this.events.triggerEvent("loadend");
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.GML"
+});
+/* ======================================================================
+    OpenLayers/Layer/PointTrack.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2007 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.PointTrack
+ * Vector layer to display ordered point features as a line, creating one
+ * LineString feature for each pair of two points.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.Vector> 
+ */
+OpenLayers.Layer.PointTrack = OpenLayers.Class(OpenLayers.Layer.Vector, {
+  
+    /**
+     * APIProperty:
+     * dataFrom  - {<OpenLayers.Layer.PointTrack.dataFrom>} optional. If the
+     *             lines should get the data/attributes from one of the two
+     *             points, creating it, which one should it be?
+     */
+    dataFrom: null,
+    
+    /**
+     * Constructor: OpenLayers.PointTrack
+     * Constructor for a new OpenLayers.PointTrack instance.
+     *
+     * Parameters:
+     * name     - {String} name of the layer
+     * options  - {Object} Optional object with properties to tag onto the
+     *            instance.
+     */    
+    initialize: function(name, options) {
+        OpenLayers.Layer.Vector.prototype.initialize.apply(this, arguments);
+    },
+        
+    /**
+     * APIMethod: addNodes
+     * Adds point features that will be used to create lines from, using point
+     * pairs. The first point of a pair will be the source node, the second
+     * will be the target node.
+     * 
+     * Parameters:
+     * pointFeatures - {Array(<OpenLayers.Feature>)}
+     * 
+     */
+    addNodes: function(pointFeatures) {
+        if (pointFeatures.length < 2) {
+            OpenLayers.Console.error(
+                    "At least two point features have to be added to create" +
+                    "a line from");
+            return;
+        }
+        
+        var lines = new Array(pointFeatures.length-1);
+        
+        var pointFeature, startPoint, endPoint;
+        for(var i=0, len=pointFeatures.length; i<len; i++) {
+            pointFeature = pointFeatures[i];
+            endPoint = pointFeature.geometry;
+            
+            if (!endPoint) {
+              var lonlat = pointFeature.lonlat;
+              endPoint = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
+            } else if(endPoint.CLASS_NAME != "OpenLayers.Geometry.Point") {
+                OpenLayers.Console.error(
+                        "Only features with point geometries are supported.");
+                return;
+            }
+            
+            if(i > 0) {
+                var attributes = (this.dataFrom != null) ?
+                        (pointFeatures[i+this.dataFrom].data ||
+                                pointFeatures[i+this.dataFrom].attributes) :
+                        null;
+                var line = new OpenLayers.Geometry.LineString([startPoint,
+                        endPoint]);
+                        
+                lines[i-1] = new OpenLayers.Feature.Vector(line, attributes);
+            }
+            
+            startPoint = endPoint;
+        }
+
+        this.addFeatures(lines);
+    },
+    
+    CLASS_NAME: "OpenLayers.Layer.PointTrack"
+});
+
+/**
+ * Constant: OpenLayers.Layer.PointTrack.dataFrom
+ * {Object} with the following keys
+ * - SOURCE_NODE: take data/attributes from the source node of the line
+ * - TARGET_NODE: take data/attributes from the target node of the line
+ */
+OpenLayers.Layer.PointTrack.dataFrom = {'SOURCE_NODE': -1, 'TARGET_NODE': 0};
+
+/* ======================================================================
+    OpenLayers/Layer/WFS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Tile/WFS.js
+ * @requires OpenLayers/Layer/Vector.js
+ * @requires OpenLayers/Layer/Markers.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WFS
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.Vector>
+ *  - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.WFS = OpenLayers.Class(
+  OpenLayers.Layer.Vector, OpenLayers.Layer.Markers, {
+
+    /**
+     * APIProperty: isBaseLayer
+     * {Boolean} WFS layer is not a base layer by default. 
+     */
+    isBaseLayer: false,
+    
+    /**
+     * Property: tile
+     * {<OpenLayers.Tile.WFS>}
+     */
+    tile: null,    
+    
+    /**
+     * APIProperty: ratio
+     * {Float} the ratio of image/tile size to map size (this is the untiled
+     *     buffer)
+     */
+    ratio: 2,
+
+    /**  
+     * Property: DEFAULT_PARAMS
+     * {Object} Hashtable of default key/value parameters
+     */
+    DEFAULT_PARAMS: { service: "WFS",
+                      version: "1.0.0",
+                      request: "GetFeature"
+                    },
+    
+    /** 
+     * APIProperty: featureClass
+     * {<OpenLayers.Feature>} If featureClass is defined, an old-style markers
+     *     based WFS layer is created instead of a new-style vector layer. If
+     *     sent, this should be a subclass of OpenLayers.Feature
+     */
+    featureClass: null,
+    
+    /**
+      * APIProperty: format
+      * {<OpenLayers.Format>} The format you want the data to be parsed with.
+      * Must be passed in the constructor. Should be a class, not an instance.
+      * This option can only be used if no featureClass is passed / vectorMode
+      * is false: if a featureClass is passed, then this parameter is ignored.
+      */
+    format: null,
+
+    /** 
+     * Property: formatObject
+     * {<OpenLayers.Format>} Internally created/managed format object, used by
+     * the Tile to parse data.
+     */
+    formatObject: null,
+
+    /**
+     * APIProperty: formatOptions
+     * {Object} Hash of options which should be passed to the format when it is
+     * created. Must be passed in the constructor.
+     */
+    formatOptions: null, 
+
+    /**
+     * Property: vectorMode
+     * {Boolean} Should be calculated automatically. Determines whether the
+     *     layer is in vector mode or marker mode.
+     */
+    vectorMode: true, 
+    
+    /**
+     * APIProperty: encodeBBOX
+     * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no', 
+     *     but some services want it that way. Default false.
+     */
+    encodeBBOX: false,
+    
+    /**
+     * APIProperty: extractAttributes 
+     * {Boolean} Should the WFS layer parse attributes from the retrieved
+     *     GML? Defaults to false. If enabled, parsing is slower, but 
+     *     attributes are available in the attributes property of 
+     *     layer features.
+     */
+    extractAttributes: false,
+
+    /**
+     * Constructor: OpenLayers.Layer.WFS
+     *
+     * Parameters:
+     * name - {String} 
+     * url - {String} 
+     * params - {Object} 
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, url, params, options) {
+        if (options == undefined) { options = {}; } 
+        
+        if (options.featureClass || 
+            !OpenLayers.Layer.Vector || 
+            !OpenLayers.Feature.Vector) {
+            this.vectorMode = false;
+        }    
+        
+        // Turn off error reporting, browsers like Safari may work
+        // depending on the setup, and we don't want an unneccesary alert.
+        OpenLayers.Util.extend(options, {'reportError': false});
+        var newArguments = [];
+        newArguments.push(name, options);
+        OpenLayers.Layer.Vector.prototype.initialize.apply(this, newArguments);
+        if (!this.renderer || !this.vectorMode) {
+            this.vectorMode = false; 
+            if (!options.featureClass) {
+                options.featureClass = OpenLayers.Feature.WFS;
+            }   
+            OpenLayers.Layer.Markers.prototype.initialize.apply(this, 
+                                                                newArguments);
+        }
+        
+        if (this.params && this.params.typename && !this.options.typename) {
+            this.options.typename = this.params.typename;
+        }
+        
+        if (!this.options.geometry_column) {
+            this.options.geometry_column = "the_geom";
+        }    
+        
+        this.params = OpenLayers.Util.applyDefaults(
+            params, 
+            OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
+        );
+        this.url = url;
+    },    
+    
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        if (this.vectorMode) {
+            OpenLayers.Layer.Vector.prototype.destroy.apply(this, arguments);
+        } else {    
+            OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+        }    
+        if (this.tile) {
+            this.tile.destroy();
+        }
+        this.tile = null;
+
+        this.ratio = null;
+        this.featureClass = null;
+        this.format = null;
+
+        if (this.formatObject && this.formatObject.destroy) {
+            this.formatObject.destroy();
+        }
+        this.formatObject = null;
+        
+        this.formatOptions = null;
+        this.vectorMode = null;
+        this.encodeBBOX = null;
+        this.extractAttributes = null;
+    },
+    
+    /**
+     * Method: setMap
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        if (this.vectorMode) {
+            OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
+            
+            var options = {
+              'extractAttributes': this.extractAttributes
+            };
+            
+            OpenLayers.Util.extend(options, this.formatOptions);
+            if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+                options.externalProjection = this.projection;
+                options.internalProjection = this.map.getProjectionObject();
+            }    
+            
+            this.formatObject = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
+        } else {    
+            OpenLayers.Layer.Markers.prototype.setMap.apply(this, arguments);
+        }    
+    },
+    
+    /** 
+     * Method: moveTo
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     * zoomChanged - {Boolean} 
+     * dragging - {Boolean} 
+     */
+    moveTo:function(bounds, zoomChanged, dragging) {
+        if (this.vectorMode) {
+            OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments);
+        } else {
+            OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+        }    
+
+        // don't load wfs features while dragging, wait for drag end
+        if (dragging) {
+            // TBD try to hide the vector layer while dragging
+            // this.setVisibility(false);
+            // this will probably help for panning performances
+            return false;
+        }
+        
+        if ( zoomChanged ) {
+            if (this.vectorMode) {
+                this.renderer.clear();
+            }
+        }
+        
+    //DEPRECATED - REMOVE IN 3.0
+        // don't load data if current zoom level doesn't match
+        if (this.options.minZoomLevel) {
+            OpenLayers.Console.warn(OpenLayers.i18n('minZoomLevelError'));
+            
+            if (this.map.getZoom() < this.options.minZoomLevel) {
+                return null;
+            }
+        }
+        
+        if (bounds == null) {
+            bounds = this.map.getExtent();
+        }
+
+        var firstRendering = (this.tile == null);
+
+        //does the new bounds to which we need to move fall outside of the 
+        // current tile's bounds?
+        var outOfBounds = (!firstRendering &&
+                           !this.tile.bounds.containsBounds(bounds));
+
+        if (zoomChanged || firstRendering || (!dragging && outOfBounds)) {
+            //determine new tile bounds
+            var center = bounds.getCenterLonLat();
+            var tileWidth = bounds.getWidth() * this.ratio;
+            var tileHeight = bounds.getHeight() * this.ratio;
+            var tileBounds = 
+                new OpenLayers.Bounds(center.lon - (tileWidth / 2),
+                                      center.lat - (tileHeight / 2),
+                                      center.lon + (tileWidth / 2),
+                                      center.lat + (tileHeight / 2));
+
+            //determine new tile size
+            var tileSize = this.map.getSize();
+            tileSize.w = tileSize.w * this.ratio;
+            tileSize.h = tileSize.h * this.ratio;
+
+            //determine new position (upper left corner of new bounds)
+            var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top);
+            var pos = this.map.getLayerPxFromLonLat(ul);
+
+            //formulate request url string
+            var url = this.getFullRequestString();
+        
+            var params = {BBOX: this.encodeBBOX ? tileBounds.toBBOX() 
+                                                : tileBounds.toArray()};
+            
+            if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+                var projectedBounds = tileBounds.clone();
+                projectedBounds.transform(this.map.getProjectionObject(), 
+                                          this.projection);
+                params.BBOX = this.encodeBBOX ? projectedBounds.toBBOX() 
+                                              : projectedBounds.toArray();
+            }                                  
+
+            url += "&" + OpenLayers.Util.getParameterString(params);
+
+            if (!this.tile) {
+                this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds, 
+                                                     url, tileSize);
+                this.addTileMonitoringHooks(this.tile);
+                this.tile.draw();
+            } else {
+                if (this.vectorMode) {
+                    this.destroyFeatures();
+                    this.renderer.clear();
+                } else {
+                    this.clearMarkers();
+                }    
+                this.removeTileMonitoringHooks(this.tile);
+                this.tile.destroy();
+                
+                this.tile = null;
+                this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds, 
+                                                     url, tileSize);
+                this.addTileMonitoringHooks(this.tile);
+                this.tile.draw();
+            } 
+        }
+    },
+
+    /** 
+     * Method: addTileMonitoringHooks
+     * This function takes a tile as input and adds the appropriate hooks to 
+     *     the tile so that the layer can keep track of the loading tile
+     *     (making sure to check that the tile is always the layer's current
+     *     tile before taking any action).
+     * 
+     * Parameters: 
+     * tile - {<OpenLayers.Tile>}
+     */
+    addTileMonitoringHooks: function(tile) {
+        tile.onLoadStart = function() {
+            //if this is the the layer's current tile, then trigger 
+            // a 'loadstart'
+            if (this == this.layer.tile) {
+                this.layer.events.triggerEvent("loadstart");
+            }
+        };
+        tile.events.register("loadstart", tile, tile.onLoadStart);
+      
+        tile.onLoadEnd = function() {
+            //if this is the the layer's current tile, then trigger 
+            // a 'tileloaded' and 'loadend'
+            if (this == this.layer.tile) {
+                this.layer.events.triggerEvent("tileloaded");
+                this.layer.events.triggerEvent("loadend");
+            }
+        };
+        tile.events.register("loadend", tile, tile.onLoadEnd);
+        tile.events.register("unload", tile, tile.onLoadEnd);
+    },
+    
+    /** 
+     * Method: removeTileMonitoringHooks
+     * This function takes a tile as input and removes the tile hooks 
+     *     that were added in addTileMonitoringHooks()
+     * 
+     * Parameters: 
+     * tile - {<OpenLayers.Tile>}
+     */
+    removeTileMonitoringHooks: function(tile) {
+        tile.unload();
+        tile.events.un({
+            "loadstart": tile.onLoadStart,
+            "loadend": tile.onLoadEnd,
+            "unload": tile.onLoadEnd,
+            scope: tile
+        });
+    },
+
+    /**
+     * Method: onMapResize
+     * Call the onMapResize method of the appropriate parent class. 
+     */
+    onMapResize: function() {
+        if(this.vectorMode) {
+            OpenLayers.Layer.Vector.prototype.onMapResize.apply(this, 
+                                                                arguments);
+        } else {
+            OpenLayers.Layer.Markers.prototype.onMapResize.apply(this, 
+                                                                 arguments);
+        }
+    },
+    
+    /**
+     * APIMethod: mergeNewParams
+     * Modify parameters for the layer and redraw.
+     * 
+     * Parameters:
+     * newParams - {Object}
+     */
+    mergeNewParams:function(newParams) {
+        var upperParams = OpenLayers.Util.upperCaseObject(newParams);
+        var newArguments = [upperParams];
+        return OpenLayers.Layer.HTTPRequest.prototype.mergeNewParams.apply(this, 
+                                                                 newArguments);
+    },
+
+    /**
+     * APIMethod: clone
+     *
+     * Parameters:
+     * obj - {Object} 
+     * 
+     * Returns:
+     * {<OpenLayers.Layer.WFS>} An exact clone of this OpenLayers.Layer.WFS
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.WFS(this.name,
+                                           this.url,
+                                           this.params,
+                                           this.options);
+        }
+
+        //get all additions from superclasses
+        if (this.vectorMode) {
+            obj = OpenLayers.Layer.Vector.prototype.clone.apply(this, [obj]);
+        } else {
+            obj = OpenLayers.Layer.Markers.prototype.clone.apply(this, [obj]);
+        }    
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    },
+
+    /** 
+     * APIMethod: getFullRequestString
+     * combine the layer's url with its params and these newParams. 
+     *   
+     *    Add the SRS parameter from 'projection' -- this is probably
+     *     more eloquently done via a setProjection() method, but this 
+     *     works for now and always.
+     *
+     * Parameters:
+     * newParams - {Object} 
+     * altUrl - {String} Use this as the url instead of the layer's url
+     */
+    getFullRequestString:function(newParams, altUrl) {
+        var projectionCode = this.projection.getCode() || this.map.getProjection();
+        this.params.SRS = (projectionCode == "none") ? null : projectionCode;
+
+        return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
+                                                    this, arguments);
+    },
+   
+    /**
+     * APIMethod: commit
+     * Write out the data to a WFS server.
+     */
+    commit: function() {
+        if (!this.writer) {
+            var options = {};
+            if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+                options.externalProjection = this.projection;
+                options.internalProjection = this.map.getProjectionObject();
+            }    
+            
+            this.writer = new OpenLayers.Format.WFS(options,this);
+        }
+
+        var data = this.writer.write(this.features);
+
+        OpenLayers.Request.POST({
+            url: this.url,
+            data: data,
+            success: this.commitSuccess,
+            failure: this.commitFailure,
+            scope: this
+        });
+    },
+
+    /**
+     * Method: commitSuccess
+     * Called when the Ajax request returns a response
+     *
+     * Parameters:
+     * response - {XmlNode} from server
+     */
+    commitSuccess: function(request) {
+        var response = request.responseText;
+        if (response.indexOf('SUCCESS') != -1) {
+            this.commitReport(OpenLayers.i18n("commitSuccess", {'response':response}));
+            
+            for(var i = 0; i < this.features.length; i++) {
+                this.features[i].state = null;
+            }    
+            // TBD redraw the layer or reset the state of features
+            // foreach features: set state to null
+        } else if (response.indexOf('FAILED') != -1 ||
+            response.indexOf('Exception') != -1) {
+            this.commitReport(OpenLayers.i18n("commitFailed", {'response':response}));
+        }
+    },
+    
+    /**
+     * Method: commitFailure
+     * Called when the Ajax request fails
+     *
+     * Parameters:
+     * response - {XmlNode} from server
+     */
+    commitFailure: function(request) {},
+    
+    /**
+     * APIMethod: commitReport 
+     * Called with a 'success' message if the commit succeeded, otherwise
+     *     a failure message, and the full request text as a second parameter.
+     *     Override this function to provide custom transaction reporting.
+     *
+     * string - {String} reporting string
+     * response - {String} full XML response
+     */
+    commitReport: function(string, response) {
+        OpenLayers.Console.userError(string);
+    },
+
+    
+    /**
+     * APIMethod: refresh
+     * Refreshes all the features of the layer
+     */
+    refresh: function() {
+        if (this.tile) {
+            if (this.vectorMode) {
+                this.renderer.clear();
+                this.features.length = 0;
+            } else {   
+                this.clearMarkers();
+                this.markers.length = 0;
+            }    
+            this.tile.draw();
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.WFS"
+});
+/* ======================================================================
+    OpenLayers/Format/Filter/v1.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1
+ * Superclass for Filter version 1 parsers.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.Filter.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * Property: namespaces
+     * {Object} Mapping of namespace aliases to namespace URIs.
+     */
+    namespaces: {
+        ogc: "http://www.opengis.net/ogc",
+        xlink: "http://www.w3.org/1999/xlink",
+        xsi: "http://www.w3.org/2001/XMLSchema-instance"
+    },
+    
+    /**
+     * Property: defaultPrefix
+     */
+    defaultPrefix: "ogc",
+
+    /**
+     * Property: schemaLocation
+     * {String} Schema location for a particular minor version.
+     */
+    schemaLocation: null,
+    
+    /**
+     * Constructor: OpenLayers.Format.Filter.v1
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.Filter> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+    
+    /**
+     * Method: read
+     *
+     * Parameters:
+     * data - {DOMElement} A Filter document element.
+     *
+     * Returns:
+     * {<OpenLayers.Filter>} A filter object.
+     */
+    read: function(data) {
+        var obj = {}
+        var filter = this.readers.ogc["Filter"].apply(this, [data, obj]);
+        return obj.filter;
+    },
+    
+    /**
+     * Property: readers
+     * Contains public functions, grouped by namespace prefix, that will
+     *     be applied when a namespaced node is found matching the function
+     *     name.  The function will be applied in the scope of this parser
+     *     with two arguments: the node being read and a context object passed
+     *     from the parent.
+     */
+    readers: {
+        "ogc": {
+            "Filter": function(node, parent) {
+                // Filters correspond to subclasses of OpenLayers.Filter.
+                // Since they contain information we don't persist, we
+                // create a temporary object and then pass on the filter
+                // (ogc:Filter) to the parent obj.
+                var obj = {
+                    fids: [],
+                    filters: []
+                };
+                this.readChildNodes(node, obj);
+                if(obj.fids.length > 0) {
+                    parent.filter = new OpenLayers.Filter.FeatureId({
+                        fids: obj.fids
+                    });
+                } else if(obj.filters.length > 0) {
+                    parent.filter = obj.filters[0];
+                }
+            },
+            "FeatureId": function(node, obj) {
+                var fid = node.getAttribute("fid");
+                if(fid) {
+                    obj.fids.push(fid);
+                }
+            },
+            "And": function(node, obj) {
+                var filter = new OpenLayers.Filter.Logical({
+                    type: OpenLayers.Filter.Logical.AND
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "Or": function(node, obj) {
+                var filter = new OpenLayers.Filter.Logical({
+                    type: OpenLayers.Filter.Logical.OR
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "Not": function(node, obj) {
+                var filter = new OpenLayers.Filter.Logical({
+                    type: OpenLayers.Filter.Logical.NOT
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsEqualTo": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.EQUAL_TO
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsNotEqualTo": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsLessThan": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.LESS_THAN
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsGreaterThan": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.GREATER_THAN
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsLessThanOrEqualTo": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsGreaterThanOrEqualTo": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsBetween": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.BETWEEN
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsLike": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.LIKE
+                });
+                this.readChildNodes(node, filter);
+                var wildCard = node.getAttribute("wildCard");
+                var singleChar = node.getAttribute("singleChar");
+                var esc = node.getAttribute("escape");
+                filter.value2regex(wildCard, singleChar, esc);
+                obj.filters.push(filter);
+            },
+            "Literal": function(node, obj) {
+                obj.value = this.getChildValue(node);
+            },
+            "PropertyName": function(node, filter) {
+                filter.property = this.getChildValue(node);
+            },
+            "LowerBoundary": function(node, filter) {
+                filter.lowerBoundary = this.readOgcExpression(node);
+            },
+            "UpperBoundary": function(node, filter) {
+                filter.upperBoundary = this.readOgcExpression(node);
+            }
+            
+        }
+    },
+    
+    /**
+     * Method: readOgcExpression
+     * Limited support for OGC expressions.
+     *
+     * Parameters:
+     * node - {DOMElement} A DOM element that contains an ogc:expression.
+     *
+     * Returns:
+     * {String} A value to be used in a symbolizer.
+     */
+    readOgcExpression: function(node) {
+        var obj = {};
+        this.readChildNodes(node, obj);
+        var value = obj.value;
+        if(!value) {
+            value = this.getChildValue(node);
+        }
+        return value;
+    },
+
+    /**
+     * Method: write
+     *
+     * Parameters:
+     * filter - {<OpenLayers.Filter>} A filter object.
+     *
+     * Returns:
+     * {DOMElement} An ogc:Filter element.
+     */
+    write: function(filter) {
+        return this.writers.ogc["Filter"].apply(this, [filter]);
+    },
+    
+    /**
+     * Property: writers
+     * As a compliment to the readers property, this structure contains public
+     *     writing functions grouped by namespace alias and named like the
+     *     node names they produce.
+     */
+    writers: {
+        "ogc": {
+            "Filter": function(filter) {
+                var node = this.createElementNSPlus("ogc:Filter");
+                var sub = filter.CLASS_NAME.split(".").pop();
+                if(sub == "FeatureId") {
+                    for(var i=0; i<filter.fids.length; ++i) {
+                        this.writeNode(node, "FeatureId", filter.fids[i]);
+                    }
+                } else {
+                    this.writeNode(node, this.getFilterType(filter), filter);
+                }
+                return node;
+            },
+            "FeatureId": function(fid) {
+                return this.createElementNSPlus("ogc:FeatureId", {
+                    attributes: {fid: fid}
+                });
+            },
+            "And": function(filter) {
+                var node = this.createElementNSPlus("ogc:And");
+                var childFilter;
+                for(var i=0; i<filter.filters.length; ++i) {
+                    childFilter = filter.filters[i];
+                    this.writeNode(
+                        node, this.getFilterType(childFilter), childFilter
+                    );
+                }
+                return node;
+            },
+            "Or": function(filter) {
+                var node = this.createElementNSPlus("ogc:Or");
+                var childFilter;
+                for(var i=0; i<filter.filters.length; ++i) {
+                    childFilter = filter.filters[i];
+                    this.writeNode(
+                        node, this.getFilterType(childFilter), childFilter
+                    );
+                }
+                return node;
+            },
+            "Not": function(filter) {
+                var node = this.createElementNSPlus("ogc:Not");
+                var childFilter = filter.filters[0];
+                this.writeNode(
+                    node, this.getFilterType(childFilter), childFilter
+                );
+                return node;
+            },
+            "PropertyIsEqualTo": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsEqualTo");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);
+                return node;
+            },
+            "PropertyIsNotEqualTo": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);
+                return node;
+            },
+            "PropertyIsLessThan": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsLessThan");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);                
+                return node;
+            },
+            "PropertyIsGreaterThan": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsGreaterThan");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);
+                return node;
+            },
+            "PropertyIsLessThanOrEqualTo": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);
+                return node;
+            },
+            "PropertyIsGreaterThanOrEqualTo": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);
+                return node;
+            },
+            "PropertyIsBetween": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsBetween");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "LowerBoundary", filter);
+                this.writeNode(node, "UpperBoundary", filter);
+                return node;
+            },
+            "PropertyIsLike": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsLike", {
+                    attributes: {
+                        wildCard: "*", singleChar: ".", escape: "!"
+                    }
+                });
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                // convert regex string to ogc string
+                this.writeNode(node, "Literal", filter.regex2value());
+                return node;
+            },
+            "PropertyName": function(filter) {
+                // no ogc:expression handling for now
+                return this.createElementNSPlus("ogc:PropertyName", {
+                    value: filter.property
+                });
+            },
+            "Literal": function(value) {
+                // no ogc:expression handling for now
+                return this.createElementNSPlus("ogc:Literal", {
+                    value: value
+                });
+            },
+            "LowerBoundary": function(filter) {
+                // no ogc:expression handling for now
+                var node = this.createElementNSPlus("ogc:LowerBoundary");
+                this.writeNode(node, "Literal", filter.lowerBoundary);
+                return node;
+            },
+            "UpperBoundary": function(filter) {
+                // no ogc:expression handling for now
+                var node = this.createElementNSPlus("ogc:UpperBoundary");
+                this.writeNode(node, "Literal", filter.upperBoundary);
+                return node;
+            },
+            "BBOX": function(filter) {
+                var node = this.createElementNSPlus("ogc:BBOX");
+                this.writeNode(node, "PropertyName", filter);
+                var gml = new OpenLayers.Format.GML();
+                node.appendChild(gml.buildGeometryNode(filter.value)); 
+                return node;
+            },
+            "DWITHIN": function(filter) {
+                var node = this.createElementNSPlus("ogc:DWithin");
+                this.writeNode(node, "PropertyName", filter);
+                var gml = new OpenLayers.Format.GML();
+                node.appendChild(gml.buildGeometryNode(filter.value));
+                this.writeNode(node, "Distance", filter);
+                return node;
+            },
+           "INTERSECTS": function(filter) {
+               var node = this.createElementNSPlus("ogc:Intersects");
+               this.writeNode(node, "PropertyName", filter);
+               var gml = new OpenLayers.Format.GML();
+               node.appendChild(gml.buildGeometryNode(filter.value));
+               return node;
+           },
+           "Distance": function(filter) {
+               return this.createElementNSPlus("ogc:Distance", 
+                   {attributes: {units: filter.distanceUnits}, 
+                    value: filter.distance});
+           }
+        }
+    },
+    
+    /**
+     * Method: getFilterType
+     */
+    getFilterType: function(filter) {
+        var filterType = this.filterMap[filter.type];
+        if(!filterType) {
+            throw "Filter writing not supported for rule type: " + filter.type;
+        }
+        return filterType;
+    },
+    
+    /**
+     * Property: filterMap
+     * {Object} Contains a member for each filter type.  Values are node names
+     *     for corresponding OGC Filter child elements.
+     */
+    filterMap: {
+        "&&": "And",
+        "||": "Or",
+        "!": "Not",
+        "==": "PropertyIsEqualTo",
+        "!=": "PropertyIsNotEqualTo",
+        "<": "PropertyIsLessThan",
+        ">": "PropertyIsGreaterThan",
+        "<=": "PropertyIsLessThanOrEqualTo",
+        ">=": "PropertyIsGreaterThanOrEqualTo",
+        "..": "PropertyIsBetween",
+        "~": "PropertyIsLike",
+        "BBOX": "BBOX",
+        "DWITHIN": "DWITHIN",
+        "INTERSECTS": "INTERSECTS"
+    },
+    
+
+    /**
+     * Methods below this point are of general use for versioned XML parsers.
+     * These are candidates for an abstract class.
+     */
+    
+    /**
+     * Method: getNamespacePrefix
+     * Get the namespace prefix for a given uri from the <namespaces> object.
+     *
+     * Returns:
+     * {String} A namespace prefix or null if none found.
+     */
+    getNamespacePrefix: function(uri) {
+        var prefix = null;
+        if(uri == null) {
+            prefix = this.namespaces[this.defaultPrefix];
+        } else {
+            var gotPrefix = false;
+            for(prefix in this.namespaces) {
+                if(this.namespaces[prefix] == uri) {
+                    gotPrefix = true;
+                    break;
+                }
+            }
+            if(!gotPrefix) {
+                prefix = null;
+            }
+        }
+        return prefix;
+    },
+
+
+    /**
+     * Method: readChildNodes
+     */
+    readChildNodes: function(node, obj) {
+        var children = node.childNodes;
+        var child, group, reader, prefix, local;
+        for(var i=0; i<children.length; ++i) {
+            child = children[i];
+            if(child.nodeType == 1) {
+                prefix = this.getNamespacePrefix(child.namespaceURI);
+                local = child.nodeName.split(":").pop();
+                group = this.readers[prefix];
+                if(group) {
+                    reader = group[local];
+                    if(reader) {
+                        reader.apply(this, [child, obj]);
+                    }
+                }
+            }
+        }
+    },
+
+    /**
+     * Method: writeNode
+     * Shorthand for applying one of the named writers and appending the
+     *     results to a node.  If a qualified name is not provided for the
+     *     second argument (and a local name is used instead), the namespace
+     *     of the parent node will be assumed.
+     *
+     * Parameters:
+     * parent - {DOMElement} Result will be appended to this node.
+     * name - {String} The name of a node to generate.  If a qualified name
+     *     (e.g. "pre:Name") is used, the namespace prefix is assumed to be
+     *     in the <writers> group.  If a local name is used (e.g. "Name") then
+     *     the namespace of the parent is assumed.
+     * obj - {Object} Structure containing data for the writer.
+     *
+     * Returns:
+     * {DOMElement} The child node.
+     */
+    writeNode: function(parent, name, obj) {
+        var prefix, local;
+        var split = name.indexOf(":");
+        if(split > 0) {
+            prefix = name.substring(0, split);
+            local = name.substring(split + 1);
+        } else {
+            prefix = this.getNamespacePrefix(parent.namespaceURI);
+            local = name;
+        }
+        var child = this.writers[prefix][local].apply(this, [obj]);
+        parent.appendChild(child);
+        return child;
+    },
+    
+    /**
+     * Method: createElementNSPlus
+     * Shorthand for creating namespaced elements with optional attributes and
+     *     child text nodes.
+     *
+     * Parameters:
+     * name - {String} The qualified node name.
+     * options - {Object} Optional object for node configuration.
+     *
+     * Returns:
+     * {Element} An element node.
+     */
+    createElementNSPlus: function(name, options) {
+        options = options || {};
+        var loc = name.indexOf(":");
+        // order of prefix preference
+        // 1. in the uri option
+        // 2. in the prefix option
+        // 3. in the qualified name
+        // 4. from the defaultPrefix
+        var uri = options.uri || this.namespaces[options.prefix];
+        if(!uri) {
+            loc = name.indexOf(":");
+            uri = this.namespaces[name.substring(0, loc)];
+        }
+        if(!uri) {
+            uri = this.namespaces[this.defaultPrefix];
+        }
+        var node = this.createElementNS(uri, name);
+        if(options.attributes) {
+            this.setAttributes(node, options.attributes);
+        }
+        if(options.value) {
+            node.appendChild(this.createTextNode(options.value));
+        }
+        return node;
+    },
+    
+    /**
+     * Method: setAttributes
+     * Set multiple attributes given key value pairs from an object.
+     *
+     * Parameters:
+     * node - {Element} An element node.
+     * obj - {Object || Array} An object whose properties represent attribute
+     *     names and values represent attribute values.  If an attribute name
+     *     is a qualified name ("prefix:local"), the prefix will be looked up
+     *     in the parsers {namespaces} object.  If the prefix is found,
+     *     setAttributeNS will be used instead of setAttribute.
+     */
+    setAttributes: function(node, obj) {
+        var value, loc, alias, uri;
+        for(var name in obj) {
+            value = obj[name].toString();
+            // check for qualified attribute name ("prefix:local")
+            uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
+            this.setAttributeNS(node, uri, name, value);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Format.Filter.v1" 
+
+});
+/* ======================================================================
+    OpenLayers/Format/SLD/v1.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/Filter.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ * @requires OpenLayers/Format/SLD.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD.v1
+ * Superclass for SLD version 1 parsers.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * Property: namespaces
+     * {Object} Mapping of namespace aliases to namespace URIs.
+     */
+    namespaces: {
+        sld: "http://www.opengis.net/sld",
+        ogc: "http://www.opengis.net/ogc",
+        xlink: "http://www.w3.org/1999/xlink",
+        xsi: "http://www.w3.org/2001/XMLSchema-instance"
+    },
+    
+    /**
+     * Property: defaultPrefix
+     */
+    defaultPrefix: "sld",
+
+    /**
+     * Property: schemaLocation
+     * {String} Schema location for a particular minor version.
+     */
+    schemaLocation: null,
+
+    /**
+     * APIProperty: defaultSymbolizer.
+     * {Object} A symbolizer with the SLD defaults.
+     */
+    defaultSymbolizer: {
+        fillColor: "#808080",
+        fillOpacity: 1,
+        strokeColor: "#000000",
+        strokeOpacity: 1,
+        strokeWidth: 1,
+        strokeDashstyle: "solid",
+        pointRadius: 3,
+        graphicName: "square"
+    },
+    
+    /**
+     * Constructor: OpenLayers.Format.SLD.v1
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.SLD> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        // extend with ogc:Filter readers and writers
+        this.readers["ogc"] = OpenLayers.Format.Filter.v1.prototype.readers["ogc"];
+        this.writers["ogc"] = OpenLayers.Format.Filter.v1.prototype.writers["ogc"];
+        // extend with custom filter methods that may get changed
+        this.readOgcExpression = OpenLayers.Format.Filter.v1.prototype.readOgcExpression;
+        this.getFilterType = OpenLayers.Format.Filter.v1.prototype.getFilterType;
+        this.filterMap = OpenLayers.Format.Filter.v1.prototype.filterMap;
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+    
+    /**
+     * Method: read
+     *
+     * Parameters:
+     * data - {DOMElement} An SLD document element.
+     *
+     * Returns:
+     * {Object} An object representing the SLD.
+     */
+    read: function(data) {        
+        var sld = {
+            namedLayers: {}
+        };
+        this.readChildNodes(data, sld);
+        return sld;
+    },
+    
+    /**
+     * Property: readers
+     * Contains public functions, grouped by namespace prefix, that will
+     *     be applied when a namespaced node is found matching the function
+     *     name.  The function will be applied in the scope of this parser
+     *     with two arguments: the node being read and a context object passed
+     *     from the parent.
+     */
+    readers: {
+        "sld": {
+            "StyledLayerDescriptor": function(node, sld) {
+                sld.version = node.getAttribute("version");
+                this.readChildNodes(node, sld);
+            },
+            "Name": function(node, obj) {
+                obj.name = this.getChildValue(node);
+            },
+            "Title": function(node, obj) {
+                obj.title = this.getChildValue(node);
+            },
+            "Abstract": function(node, obj) {
+                obj.description = this.getChildValue(node);
+            },
+            "NamedLayer": function(node, sld) {
+                var layer = {
+                    userStyles: [],
+                    namedStyles: []
+                };
+                this.readChildNodes(node, layer);
+                // give each of the user styles this layer name
+                for(var i=0, len=layer.userStyles.length; i<len; ++i) {
+                    layer.userStyles[i].layerName = layer.name;
+                }
+                sld.namedLayers[layer.name] = layer;
+            },
+            "NamedStyle": function(node, layer) {
+                layer.namedStyles.push(
+                    this.getChildName(node.firstChild)
+                );
+            },
+            "UserStyle": function(node, layer) {
+                var style = new OpenLayers.Style(this.defaultSymbolizer);
+                this.readChildNodes(node, style);
+                layer.userStyles.push(style);
+            },
+            "IsDefault": function(node, style) {
+                if(this.getChildValue(node) == "1") {
+                    style.isDefault = true;
+                }
+            },
+            "FeatureTypeStyle": function(node, style) {
+                // OpenLayers doesn't have a place for FeatureTypeStyle
+                // Name, Title, Abstract, FeatureTypeName, or
+                // SemanticTypeIdentifier so, we make a temporary object
+                // and later just use the Rule(s).
+                var obj = {
+                    rules: []
+                };
+                this.readChildNodes(node, obj);
+                style.rules = obj.rules;
+            },
+            "Rule": function(node, obj) {
+                var rule = new OpenLayers.Rule();
+                this.readChildNodes(node, rule);
+                obj.rules.push(rule);
+            },
+            "ElseFilter": function(node, rule) {
+                rule.elseFilter = true;
+            },
+            "MinScaleDenominator": function(node, rule) {
+                rule.minScaleDenominator = this.getChildValue(node);
+            },
+            "MaxScaleDenominator": function(node, rule) {
+                rule.maxScaleDenominator = this.getChildValue(node);
+            },
+            "LineSymbolizer": function(node, rule) {
+                // OpenLayers doens't do painter's order, instead we extend
+                var symbolizer = rule.symbolizer["Line"] || {};
+                this.readChildNodes(node, symbolizer);
+                // in case it didn't exist before
+                rule.symbolizer["Line"] = symbolizer;
+            },
+            "PolygonSymbolizer": function(node, rule) {
+                // OpenLayers doens't do painter's order, instead we extend
+                var symbolizer = rule.symbolizer["Polygon"] || {};
+                this.readChildNodes(node, symbolizer);
+                // in case it didn't exist before
+                rule.symbolizer["Polygon"] = symbolizer;
+            },
+            "PointSymbolizer": function(node, rule) {
+                // OpenLayers doens't do painter's order, instead we extend
+                var symbolizer = rule.symbolizer["Point"] || {};
+                this.readChildNodes(node, symbolizer);
+                // in case it didn't exist before
+                rule.symbolizer["Point"] = symbolizer;
+            },
+            "Stroke": function(node, symbolizer) {
+                this.readChildNodes(node, symbolizer);
+            },
+            "Fill": function(node, symbolizer) {
+                this.readChildNodes(node, symbolizer);
+            },
+            "CssParameter": function(node, symbolizer) {
+                var cssProperty = node.getAttribute("name");
+                var symProperty = this.cssMap[cssProperty];
+                if(symProperty) {
+                    // Limited support for parsing of OGC expressions
+                    var value = this.readOgcExpression(node);
+                    // always string, could be an empty string
+                    if(value) {
+                        symbolizer[symProperty] = value;
+                    }
+                }
+            },
+            "Graphic": function(node, symbolizer) {
+                var graphic = {};
+                // painter's order not respected here, clobber previous with next
+                this.readChildNodes(node, graphic);
+                // directly properties with names that match symbolizer properties
+                var properties = [
+                    "strokeColor", "strokeWidth", "strokeOpacity",
+                    "strokeLinecap", "fillColor", "fillOpacity",
+                    "graphicName", "rotation", "graphicFormat"
+                ];
+                var prop, value;
+                for(var i=0, len=properties.length; i<len; ++i) {
+                    prop = properties[i];
+                    value = graphic[prop];
+                    if(value != undefined) {
+                        symbolizer[prop] = value;
+                    }
+                }
+                // set other generic properties with specific graphic property names
+                if(graphic.opacity != undefined) {
+                    symbolizer.graphicOpacity = graphic.opacity;
+                }
+                if(graphic.size != undefined) {
+                    symbolizer.pointRadius = graphic.size / 2;
+                }
+                if(graphic.href != undefined) {
+                    symbolizer.externalGraphic = graphic.href;
+                }
+                if(graphic.rotation != undefined) {
+                    symbolizer.rotation = graphic.rotation;
+                }
+            },
+            "ExternalGraphic": function(node, graphic) {
+                this.readChildNodes(node, graphic);
+            },
+            "Mark": function(node, graphic) {
+                this.readChildNodes(node, graphic);
+            },
+            "WellKnownName": function(node, graphic) {
+                graphic.graphicName = this.getChildValue(node);
+            },
+            "Opacity": function(node, obj) {
+                // No support for parsing of OGC expressions
+                var opacity = this.getChildValue(node);
+                // always string, could be empty string
+                if(opacity) {
+                    obj.opacity = opacity;
+                }
+            },
+            "Size": function(node, obj) {
+                // No support for parsing of OGC expressions
+                var size = this.getChildValue(node);
+                // always string, could be empty string
+                if(size) {
+                    obj.size = size;
+                }
+            },
+            "Rotation": function(node, obj) {
+                // No support for parsing of OGC expressions
+                var rotation = this.getChildValue(node);
+                // always string, could be empty string
+                if(rotation) {
+                    obj.rotation = rotation;
+                }
+            },
+            "OnlineResource": function(node, obj) {
+                obj.href = this.getAttributeNS(
+                    node, this.namespaces.xlink, "href"
+                );
+            },
+            "Format": function(node, graphic) {
+                graphic.graphicFormat = this.getChildValue(node);
+            }
+        }
+    },
+    
+    /**
+     * Property: cssMap
+     * {Object} Object mapping supported css property names to OpenLayers
+     *     symbolizer property names.
+     */
+    cssMap: {
+        "stroke": "strokeColor",
+        "stroke-opacity": "strokeOpacity",
+        "stroke-width": "strokeWidth",
+        "stroke-linecap": "strokeLinecap",
+        "stroke-dasharray": "strokeDashstyle",
+        "fill": "fillColor",
+        "fill-opacity": "fillOpacity",
+        "font-family": "fontFamily",
+        "font-size": "fontSize"
+    },
+    
+    /**
+     * Method: getCssProperty
+     * Given a symbolizer property, get the corresponding CSS property
+     *     from the <cssMap>.
+     *
+     * Parameters:
+     * sym - {String} A symbolizer property name.
+     *
+     * Returns:
+     * {String} A CSS property name or null if none found.
+     */
+    getCssProperty: function(sym) {
+        var css = null;
+        for(var prop in this.cssMap) {
+            if(this.cssMap[prop] == sym) {
+                css = prop;
+                break;
+            }
+        }
+        return css;
+    },
+    
+    /**
+     * Method: getGraphicFormat
+     * Given a href for an external graphic, try to determine the mime-type.
+     *     This method doesn't try too hard, and will fall back to
+     *     <defautlGraphicFormat> if one of the known <graphicFormats> is not
+     *     the file extension of the provided href.
+     *
+     * Parameters:
+     * href - {String}
+     *
+     * Returns:
+     * {String} The graphic format.
+     */
+    getGraphicFormat: function(href) {
+        var format, regex;
+        for(var key in this.graphicFormats) {
+            if(this.graphicFormats[key].test(href)) {
+                format = key;
+                break;
+            }
+        }
+        return format || this.defautlGraphicFormat;
+    },
+    
+    /**
+     * Property: defaultGraphicFormat
+     * {String} If none other can be determined from <getGraphicFormat>, this
+     *     default will be returned.
+     */
+    defaultGraphicFormat: "image/png",
+    
+    /**
+     * Property: graphicFormats
+     * {Object} Mapping of image mime-types to regular extensions matching 
+     *     well-known file extensions.
+     */
+    graphicFormats: {
+        "image/jpeg": /\.jpe?g$/i,
+        "image/gif": /\.gif$/i,
+        "image/png": /\.png$/i
+    },
+
+    /**
+     * Method: write
+     *
+     * Parameters:
+     * sld - {Object} An object representing the SLD.
+     *
+     * Returns:
+     * {DOMElement} The root of an SLD document.
+     */
+    write: function(sld) {
+        return this.writers.sld.StyledLayerDescriptor.apply(this, [sld]);
+    },
+    
+    /**
+     * Property: writers
+     * As a compliment to the readers property, this structure contains public
+     *     writing functions grouped by namespace alias and named like the
+     *     node names they produce.
+     */
+    writers: {
+        "sld": {
+            "StyledLayerDescriptor": function(sld) {
+                var root = this.createElementNSPlus(
+                    "StyledLayerDescriptor",
+                    {attributes: {
+                        "version": this.VERSION,
+                        "xsi:schemaLocation": this.schemaLocation
+                    }}
+                );
+                // add in optional name
+                if(sld.name) {
+                    this.writeNode(root, "Name", sld.name);
+                }
+                // add in optional title
+                if(sld.title) {
+                    this.writeNode(root, "Title", sld.title);
+                }
+                // add in optional description
+                if(sld.description) {
+                    this.writeNode(root, "Abstract", sld.description);
+                }
+                // add in named layers
+                for(var name in sld.namedLayers) {
+                    this.writeNode(root, "NamedLayer", sld.namedLayers[name]);
+                }
+                return root;
+            },
+            "Name": function(name) {
+                return this.createElementNSPlus("Name", {value: name});
+            },
+            "Title": function(title) {
+                return this.createElementNSPlus("Title", {value: title});
+            },
+            "Abstract": function(description) {
+                return this.createElementNSPlus(
+                    "Abstract", {value: description}
+                );
+            },
+            "NamedLayer": function(layer) {
+                var node = this.createElementNSPlus("NamedLayer");
+
+                // add in required name
+                this.writeNode(node, "Name", layer.name);
+
+                // optional sld:LayerFeatureConstraints here
+
+                // add in named styles
+                if(layer.namedStyles) {
+                    for(var i=0, len=layer.namedStyles.length; i<len; ++i) {
+                        this.writeNode(
+                            node, "NamedStyle", layer.namedStyles[i]
+                        );
+                    }
+                }
+                
+                // add in user styles
+                if(layer.userStyles) {
+                    for(var i=0, len=layer.userStyles.length; i<len; ++i) {
+                        this.writeNode(
+                            node, "UserStyle", layer.userStyles[i]
+                        );
+                    }
+                }
+                
+                return node;
+            },
+            "NamedStyle": function(name) {
+                var node = this.createElementNSPlus("NamedStyle");
+                this.writeNode(node, "Name", name);
+                return node;
+            },
+            "UserStyle": function(style) {
+                var node = this.createElementNSPlus("UserStyle");
+
+                // add in optional name
+                if(style.name) {
+                    this.writeNode(node, "Name", style.name);
+                }
+                // add in optional title
+                if(style.title) {
+                    this.writeNode(node, "Title", style.title);
+                }
+                // add in optional description
+                if(style.description) {
+                    this.writeNode(node, "Abstract", style.description);
+                }
+                
+                // add isdefault
+                if(style.isDefault) {
+                    this.writeNode(node, "IsDefault", style.isDefault);
+                }
+                
+                // add FeatureTypeStyles
+                this.writeNode(node, "FeatureTypeStyle", style);
+                
+                return node;
+            },
+            "IsDefault": function(bool) {
+                return this.createElementNSPlus(
+                    "IsDefault", {value: (bool) ? "1" : "0"}
+                );
+            },
+            "FeatureTypeStyle": function(style) {
+                var node = this.createElementNSPlus("FeatureTypeStyle");
+                
+                // OpenLayers currently stores no Name, Title, Abstract,
+                // FeatureTypeName, or SemanticTypeIdentifier information
+                // related to FeatureTypeStyle
+                
+                // add in rules
+                for(var i=0, len=style.rules.length; i<len; ++i) {
+                    this.writeNode(node, "Rule", style.rules[i]);
+                }
+                
+                return node;
+            },
+            "Rule": function(rule) {
+                var node = this.createElementNSPlus("Rule");
+
+                // add in optional name
+                if(rule.name) {
+                    this.writeNode(node, "Name", rule.name);
+                }
+                // add in optional title
+                if(rule.title) {
+                    this.writeNode(node, "Title", rule.title);
+                }
+                // add in optional description
+                if(rule.description) {
+                    this.writeNode(node, "Abstract", rule.description);
+                }
+                
+                // add in LegendGraphic here
+                
+                // add in optional filters
+                if(rule.elseFilter) {
+                    this.writeNode(node, "ElseFilter");
+                } else if(rule.filter) {
+                    this.writeNode(node, "ogc:Filter", rule.filter);
+                }
+                
+                // add in scale limits
+                if(rule.minScaleDenominator != undefined) {
+                    this.writeNode(
+                        node, "MinScaleDenominator", rule.minScaleDenominator
+                    );
+                }
+                if(rule.maxScaleDenominator != undefined) {
+                    this.writeNode(
+                        node, "MaxScaleDenominator", rule.maxScaleDenominator
+                    );
+                }
+                
+                // add in symbolizers (relies on geometry type keys)
+                var types = OpenLayers.Style.SYMBOLIZER_PREFIXES;
+                var type, symbolizer;
+                for(var i=0, len=types.length; i<len; ++i) {
+                    type = types[i];
+                    symbolizer = rule.symbolizer[type];
+                    if(symbolizer) {
+                        this.writeNode(
+                            node, type + "Symbolizer", symbolizer
+                        );
+                    }
+                }
+                return node;
+
+            },
+            "ElseFilter": function() {
+                return this.createElementNSPlus("ElseFilter");
+            },
+            "MinScaleDenominator": function(scale) {
+                return this.createElementNSPlus(
+                    "MinScaleDenominator", {value: scale}
+                );
+            },
+            "MaxScaleDenominator": function(scale) {
+                return this.createElementNSPlus(
+                    "MaxScaleDenominator", {value: scale}
+                );
+            },
+            "LineSymbolizer": function(symbolizer) {
+                var node = this.createElementNSPlus("LineSymbolizer");
+                this.writeNode(node, "Stroke", symbolizer);
+                return node;
+            },
+            "Stroke": function(symbolizer) {
+                var node = this.createElementNSPlus("Stroke");
+
+                // GraphicFill here
+                // GraphicStroke here
+
+                // add in CssParameters
+                if(symbolizer.strokeColor != undefined) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "strokeColor"}
+                    );
+                }
+                if(symbolizer.strokeOpacity != undefined) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "strokeOpacity"}
+                    );
+                }
+                if(symbolizer.strokeWidth != undefined) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "strokeWidth"}
+                    );
+                }
+                return node;
+            },
+            "CssParameter": function(obj) {
+                // not handling ogc:expressions for now
+                return this.createElementNSPlus("CssParameter", {
+                    attributes: {name: this.getCssProperty(obj.key)},
+                    value: obj.symbolizer[obj.key]
+                });
+            },
+            "TextSymbolizer": function(symbolizer) {
+                var node = this.createElementNSPlus("TextSymbolizer");
+                // add in optional Label
+                if(symbolizer.label != null) {
+                    this.writeNode(node, "Label", symbolizer.label);
+                }
+                // add in optional Font
+                if(symbolizer.fontFamily != null ||
+                   symbolizer.fontSize != null) {
+                    this.writeNode(node, "Font", symbolizer);
+                }
+                // add in optional Fill
+                if(symbolizer.fillColor != null ||
+                   symbolizer.fillOpacity != null) {
+                    this.writeNode(node, "Fill", symbolizer);
+                }
+                return node;
+            },
+            "Font": function(symbolizer) {
+                var node = this.createElementNSPlus("Font");
+                // add in CssParameters
+                if(symbolizer.fontFamily) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "fontFamily"}
+                    );
+                }
+                if(symbolizer.fontSize) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "fontSize"}
+                    );
+                }
+                return node;
+            },
+            "Label": function(label) {
+                // only the simplest of ogc:expression handled
+                // {label: "some text and a ${propertyName}"}
+                var node = this.createElementNSPlus("Label");
+                var tokens = label.split("${");
+                node.appendChild(this.createTextNode(tokens[0]));
+                var item, last;
+                for(var i=1, len=tokens.length; i<len; i++) {
+                    item = tokens[i];
+                    last = item.indexOf("}"); 
+                    if(last > 0) {
+                        this.writeNode(
+                            node, "ogc:PropertyName",
+                            {property: item.substring(0, last)}
+                        );
+                        node.appendChild(
+                            this.createTextNode(item.substring(++last))
+                        );
+                    } else {
+                        // no ending }, so this is a literal ${
+                        node.appendChild(
+                            this.createTextNode("${" + item)
+                        );
+                    }
+                }
+                return node;
+            },
+            "PolygonSymbolizer": function(symbolizer) {
+                var node = this.createElementNSPlus("PolygonSymbolizer");
+                this.writeNode(node, "Fill", symbolizer);
+                this.writeNode(node, "Stroke", symbolizer);
+                return node;
+            },
+            "Fill": function(symbolizer) {
+                var node = this.createElementNSPlus("Fill");
+                
+                // GraphicFill here
+                
+                // add in CssParameters
+                if(symbolizer.fillColor) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "fillColor"}
+                    );
+                }
+                if(symbolizer.fillOpacity) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "fillOpacity"}
+                    );
+                }
+                return node;
+            },
+            "PointSymbolizer": function(symbolizer) {
+                var node = this.createElementNSPlus("PointSymbolizer");
+                this.writeNode(node, "Graphic", symbolizer);
+                return node;
+            },
+            "Graphic": function(symbolizer) {
+                var node = this.createElementNSPlus("Graphic");
+                if(symbolizer.externalGraphic != undefined) {
+                    this.writeNode(node, "ExternalGraphic", symbolizer);
+                } else if(symbolizer.graphicName) {
+                    this.writeNode(node, "Mark", symbolizer);
+                }
+                
+                if(symbolizer.graphicOpacity != undefined) {
+                    this.writeNode(node, "Opacity", symbolizer.graphicOpacity);
+                }
+                if(symbolizer.pointRadius != undefined) {
+                    this.writeNode(node, "Size", symbolizer.pointRadius * 2);
+                }
+                if(symbolizer.rotation != undefined) {
+                    this.writeNode(node, "Rotation", symbolizer.rotation);
+                }
+                return node;
+            },
+            "ExternalGraphic": function(symbolizer) {
+                var node = this.createElementNSPlus("ExternalGraphic");
+                this.writeNode(
+                    node, "OnlineResource", symbolizer.externalGraphic
+                );
+                var format = symbolizer.graphicFormat ||
+                             this.getGraphicFormat(symbolizer.externalGraphic);
+                this.writeNode(node, "Format", format);
+                return node;
+            },
+            "Mark": function(symbolizer) {
+                var node = this.createElementNSPlus("Mark");
+                this.writeNode(node, "WellKnownName", symbolizer.graphicName);
+                this.writeNode(node, "Fill", symbolizer);
+                this.writeNode(node, "Stroke", symbolizer);
+                return node;
+            },
+            "WellKnownName": function(name) {
+                return this.createElementNSPlus("WellKnownName", {
+                    value: name
+                });
+            },
+            "Opacity": function(value) {
+                return this.createElementNSPlus("Opacity", {
+                    value: value
+                });
+            },
+            "Size": function(value) {
+                return this.createElementNSPlus("Size", {
+                    value: value
+                });
+            },
+            "Rotation": function(value) {
+                return this.createElementNSPlus("Rotation", {
+                    value: value
+                });
+            },
+            "OnlineResource": function(href) {
+                return this.createElementNSPlus("OnlineResource", {
+                    attributes: {
+                        "xlink:type": "simple",
+                        "xlink:href": href
+                    }
+                });
+            },
+            "Format": function(format) {
+                return this.createElementNSPlus("Format", {
+                    value: format
+                });
+            }
+        }
+    },
+    
+    /**
+     * Methods below this point are of general use for versioned XML parsers.
+     * These are candidates for an abstract class.
+     */
+    
+    /**
+     * Method: getNamespacePrefix
+     * Get the namespace prefix for a given uri from the <namespaces> object.
+     *
+     * Returns:
+     * {String} A namespace prefix or null if none found.
+     */
+    getNamespacePrefix: function(uri) {
+        var prefix = null;
+        if(uri == null) {
+            prefix = this.namespaces[this.defaultPrefix];
+        } else {
+            var gotPrefix = false;
+            for(prefix in this.namespaces) {
+                if(this.namespaces[prefix] == uri) {
+                    gotPrefix = true;
+                    break;
+                }
+            }
+            if(!gotPrefix) {
+                prefix = null;
+            }
+        }
+        return prefix;
+    },
+
+
+    /**
+     * Method: readChildNodes
+     */
+    readChildNodes: function(node, obj) {
+        var children = node.childNodes;
+        var child, group, reader, prefix, local;
+        for(var i=0, len=children.length; i<len; ++i) {
+            child = children[i];
+            if(child.nodeType == 1) {
+                prefix = this.getNamespacePrefix(child.namespaceURI);
+                local = child.nodeName.split(":").pop();
+                group = this.readers[prefix];
+                if(group) {
+                    reader = group[local];
+                    if(reader) {
+                        reader.apply(this, [child, obj]);
+                    }
+                }
+            }
+        }
+    },
+
+    /**
+     * Method: writeNode
+     * Shorthand for applying one of the named writers and appending the
+     *     results to a node.  If a qualified name is not provided for the
+     *     second argument (and a local name is used instead), the namespace
+     *     of the parent node will be assumed.
+     *
+     * Parameters:
+     * parent - {DOMElement} Result will be appended to this node.
+     * name - {String} The name of a node to generate.  If a qualified name
+     *     (e.g. "pre:Name") is used, the namespace prefix is assumed to be
+     *     in the <writers> group.  If a local name is used (e.g. "Name") then
+     *     the namespace of the parent is assumed.
+     * obj - {Object} Structure containing data for the writer.
+     *
+     * Returns:
+     * {DOMElement} The child node.
+     */
+    writeNode: function(parent, name, obj) {
+        var prefix, local;
+        var split = name.indexOf(":");
+        if(split > 0) {
+            prefix = name.substring(0, split);
+            local = name.substring(split + 1);
+        } else {
+            prefix = this.getNamespacePrefix(parent.namespaceURI);
+            local = name;
+        }
+        var child = this.writers[prefix][local].apply(this, [obj]);
+        parent.appendChild(child);
+        return child;
+    },
+    
+    /**
+     * Method: createElementNSPlus
+     * Shorthand for creating namespaced elements with optional attributes and
+     *     child text nodes.
+     *
+     * Parameters:
+     * name - {String} The qualified node name.
+     * options - {Object} Optional object for node configuration.
+     *
+     * Returns:
+     * {Element} An element node.
+     */
+    createElementNSPlus: function(name, options) {
+        options = options || {};
+        var loc = name.indexOf(":");
+        // order of prefix preference
+        // 1. in the uri option
+        // 2. in the prefix option
+        // 3. in the qualified name
+        // 4. from the defaultPrefix
+        var uri = options.uri || this.namespaces[options.prefix];
+        if(!uri) {
+            loc = name.indexOf(":");
+            uri = this.namespaces[name.substring(0, loc)];
+        }
+        if(!uri) {
+            uri = this.namespaces[this.defaultPrefix];
+        }
+        var node = this.createElementNS(uri, name);
+        if(options.attributes) {
+            this.setAttributes(node, options.attributes);
+        }
+        if(options.value) {
+            node.appendChild(this.createTextNode(options.value));
+        }
+        return node;
+    },
+    
+    /**
+     * Method: setAttributes
+     * Set multiple attributes given key value pairs from an object.
+     *
+     * Parameters:
+     * node - {Element} An element node.
+     * obj - {Object || Array} An object whose properties represent attribute
+     *     names and values represent attribute values.  If an attribute name
+     *     is a qualified name ("prefix:local"), the prefix will be looked up
+     *     in the parsers {namespaces} object.  If the prefix is found,
+     *     setAttributeNS will be used instead of setAttribute.
+     */
+    setAttributes: function(node, obj) {
+        var value, loc, alias, uri;
+        for(var name in obj) {
+            value = obj[name].toString();
+            // check for qualified attribute name ("prefix:local")
+            uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
+            this.setAttributeNS(node, uri, name, value);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Format.SLD.v1" 
+
+});
+/* ======================================================================
+    OpenLayers/Geometry/Curve.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Curve
+ * A Curve is a MultiPoint, whose points are assumed to be connected. To 
+ * this end, we provide a "getLength()" function, which iterates through 
+ * the points, summing the distances between them. 
+ * 
+ * Inherits: 
+ *  - <OpenLayers.Geometry.MultiPoint>
+ */
+OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of 
+     *                 components that the collection can include.  A null 
+     *                 value means the component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.Point"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.Curve
+     * 
+     * Parameters:
+     * point - {Array(<OpenLayers.Geometry.Point>)}
+     */
+    initialize: function(points) {
+        OpenLayers.Geometry.MultiPoint.prototype.initialize.apply(this, 
+                                                                  arguments);
+    },
+    
+    /**
+     * APIMethod: getLength
+     * 
+     * Returns:
+     * {Float} The length of the curve
+     */
+    getLength: function() {
+        var length = 0.0;
+        if ( this.components && (this.components.length > 1)) {
+            for(var i=1, len=this.components.length; i<len; i++) {
+                length += this.components[i-1].distanceTo(this.components[i]);
+            }
+        }
+        return length;
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.Curve"
+});
+/* ======================================================================
+    OpenLayers/Format/Filter/v1_0_0.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/Filter/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1_0_0
+ * Write ogc:Filter version 1.0.0.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.Filter.v1>
+ */
+OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class(
+    OpenLayers.Format.Filter.v1, {
+    
+    /**
+     * Constant: VERSION
+     * {String} 1.0.0
+     */
+    VERSION: "1.0.0",
+    
+    /**
+     * Property: schemaLocation
+     * {String} http://www.opengis.net/ogc/filter/1.0.0/filter.xsd
+     */
+    schemaLocation: "http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",
+
+    /**
+     * Constructor: OpenLayers.Format.Filter.v1_0_0
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.Filter> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.Filter.v1.prototype.initialize.apply(
+            this, [options]
+        );
+    },
+
+    CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0" 
+
+});
+/* ======================================================================
+    OpenLayers/Format/SLD/v1_0_0.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/SLD/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD.v1_0_0
+ * Write SLD version 1.0.0.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.SLD.v1>
+ */
+OpenLayers.Format.SLD.v1_0_0 = OpenLayers.Class(
+    OpenLayers.Format.SLD.v1, {
+    
+    /**
+     * Constant: VERSION
+     * {String} 1.0.0
+     */
+    VERSION: "1.0.0",
+    
+    /**
+     * Property: schemaLocation
+     * {String} http://www.opengis.net/sld
+     *   http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd
+     */
+    schemaLocation: "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd",
+
+    /**
+     * Constructor: OpenLayers.Format.SLD.v1_0_0
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.SLD> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.SLD.v1.prototype.initialize.apply(
+            this, [options]
+        );
+    },
+
+    CLASS_NAME: "OpenLayers.Format.SLD.v1_0_0" 
+
+});
+/* ======================================================================
+    OpenLayers/Geometry/LineString.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Curve.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LineString
+ * A LineString is a Curve which, once two points have been added to it, can 
+ * never be less than two points long.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Geometry.Curve>
+ */
+OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
+
+    /**
+     * Constructor: OpenLayers.Geometry.LineString
+     * Create a new LineString geometry
+     *
+     * Parameters:
+     * points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to
+     *          generate the linestring
+     *
+     */
+    initialize: function(points) {
+        OpenLayers.Geometry.Curve.prototype.initialize.apply(this, arguments);        
+    },
+
+    /**
+     * APIMethod: removeComponent
+     * Only allows removal of a point if there are three or more points in 
+     * the linestring. (otherwise the result would be just a single point)
+     *
+     * Parameters: 
+     * point - {<OpenLayers.Geometry.Point>} The point to be removed
+     */
+    removeComponent: function(point) {
+        if ( this.components && (this.components.length > 2)) {
+            OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, 
+                                                                  arguments);
+        }
+    },
+    
+    /**
+     * APIMethod: intersects
+     * Test for instersection between two geometries.  This is a cheapo
+     *     implementation of the Bently-Ottmann algorigithm.  It doesn't
+     *     really keep track of a sweep line data structure.  It is closer
+     *     to the brute force method, except that segments are sorted and
+     *     potential intersections are only calculated when bounding boxes
+     *     intersect.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     *
+     * Returns:
+     * {Boolean} The input geometry intersects this geometry.
+     */
+    intersects: function(geometry) {
+        var intersect = false;
+        var type = geometry.CLASS_NAME;
+        if(type == "OpenLayers.Geometry.LineString" ||
+           type == "OpenLayers.Geometry.LinearRing" ||
+           type == "OpenLayers.Geometry.Point") {
+            var segs1 = this.getSortedSegments();
+            var segs2;
+            if(type == "OpenLayers.Geometry.Point") {
+                segs2 = [{
+                    x1: geometry.x, y1: geometry.y,
+                    x2: geometry.x, y2: geometry.y
+                }];
+            } else {
+                segs2 = geometry.getSortedSegments();
+            }
+            var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
+                seg2, seg2y1, seg2y2;
+            // sweep right
+            outer: for(var i=0, len=segs1.length; i<len; ++i) {
+                seg1 = segs1[i];
+                seg1x1 = seg1.x1;
+                seg1x2 = seg1.x2;
+                seg1y1 = seg1.y1;
+                seg1y2 = seg1.y2;
+                inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
+                    seg2 = segs2[j];
+                    if(seg2.x1 > seg1x2) {
+                        // seg1 still left of seg2
+                        break;
+                    }
+                    if(seg2.x2 < seg1x1) {
+                        // seg2 still left of seg1
+                        continue;
+                    }
+                    seg2y1 = seg2.y1;
+                    seg2y2 = seg2.y2;
+                    if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) {
+                        // seg2 above seg1
+                        continue;
+                    }
+                    if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) {
+                        // seg2 below seg1
+                        continue;
+                    }
+                    if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) {
+                        intersect = true;
+                        break outer;
+                    }
+                }
+            }
+        } else {
+            intersect = geometry.intersects(this);
+        }
+        return intersect;
+    },
+    
+    /**
+     * Method: getSortedSegments
+     *
+     * Returns:
+     * {Array} An array of segment objects.  Segment objects have properties
+     *     x1, y1, x2, and y2.  The start point is represented by x1 and y1.
+     *     The end point is represented by x2 and y2.  Start and end are
+     *     ordered so that x1 < x2.
+     */
+    getSortedSegments: function() {
+        var numSeg = this.components.length - 1;
+        var segments = new Array(numSeg);
+        for(var i=0; i<numSeg; ++i) {
+            point1 = this.components[i];
+            point2 = this.components[i + 1];
+            if(point1.x < point2.x) {
+                segments[i] = {
+                    x1: point1.x,
+                    y1: point1.y,
+                    x2: point2.x,
+                    y2: point2.y
+                };
+            } else {
+                segments[i] = {
+                    x1: point2.x,
+                    y1: point2.y,
+                    x2: point1.x,
+                    y2: point1.y
+                };
+            }
+        }
+        // more efficient to define this somewhere static
+        function byX1(seg1, seg2) {
+            return seg1.x1 - seg2.x1;
+        }
+        return segments.sort(byX1);
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.LineString"
+});
+/* ======================================================================
+    OpenLayers/Format/GML.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML
+ * Read/Wite GML. Create a new instance with the <OpenLayers.Format.GML>
+ *     constructor.  Supports the GML simple features profile.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /*
+     * APIProperty: featureNS
+     * {String} Namespace used for feature attributes.  Default is
+     *     "http://mapserver.gis.umn.edu/mapserver".
+     */
+    featureNS: "http://mapserver.gis.umn.edu/mapserver",
+    
+    /**
+     * APIProperty: featurePrefix
+     * {String} Namespace alias (or prefix) for feature nodes.  Default is
+     *     "feature".
+     */
+    featurePrefix: "feature",
+    
+    /*
+     * APIProperty: featureName
+     * {String} Element name for features. Default is "featureMember".
+     */
+    featureName: "featureMember", 
+    
+    /*
+     * APIProperty: layerName
+     * {String} Name of data layer. Default is "features".
+     */
+    layerName: "features",
+    
+    /**
+     * APIProperty: geometryName
+     * {String} Name of geometry element.  Defaults to "geometry".
+     */
+    geometryName: "geometry",
+    
+    /** 
+     * APIProperty: collectionName
+     * {String} Name of featureCollection element.
+     */
+    collectionName: "FeatureCollection",
+    
+    /**
+     * APIProperty: gmlns
+     * {String} GML Namespace.
+     */
+    gmlns: "http://www.opengis.net/gml",
+
+    /**
+     * APIProperty: extractAttributes
+     * {Boolean} Extract attributes from GML.
+     */
+    extractAttributes: true,
+    
+    /**
+     * APIProperty: xy
+     * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+     * Changing is not recommended, a new Format should be instantiated.
+     */ 
+    xy: true,
+    
+    /**
+     * Constructor: OpenLayers.Format.GML
+     * Create a new parser for GML.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        // compile regular expressions once instead of every time they are used
+        this.regExes = {
+            trimSpace: (/^\s*|\s*$/g),
+            removeSpace: (/\s*/g),
+            splitSpace: (/\s+/),
+            trimComma: (/\s*,\s*/g)
+        };
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: read
+     * Read data from a string, and return a list of features. 
+     * 
+     * Parameters:
+     * data - {String} or {DOMElement} data to read/parse.
+     *
+     * Returns:
+     * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+     */
+    read: function(data) {
+        if(typeof data == "string") { 
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var featureNodes = this.getElementsByTagNameNS(data.documentElement,
+                                                       this.gmlns,
+                                                       this.featureName);
+        var features = [];
+        for(var i=0; i<featureNodes.length; i++) {
+            var feature = this.parseFeature(featureNodes[i]);
+            if(feature) {
+                features.push(feature);
+            }
+        }
+        return features;
+    },
+    
+    /**
+     * Method: parseFeature
+     * This function is the core of the GML parsing code in OpenLayers.
+     *    It creates the geometries that are then attached to the returned
+     *    feature, and calls parseAttributes() to get attribute data out.
+     *    
+     * Parameters:
+     * node - {DOMElement} A GML feature node. 
+     */
+    parseFeature: function(node) {
+        // only accept on geometry per feature - look for highest "order"
+        var order = ["MultiPolygon", "Polygon",
+                     "MultiLineString", "LineString",
+                     "MultiPoint", "Point", "Envelope"];
+        var type, nodeList, geometry, parser;
+        for(var i=0; i<order.length; ++i) {
+            type = order[i];
+            nodeList = this.getElementsByTagNameNS(node, this.gmlns, type);
+            if(nodeList.length > 0) {
+                // only deal with first geometry of this type
+                var parser = this.parseGeometry[type.toLowerCase()];
+                if(parser) {
+                    geometry = parser.apply(this, [nodeList[0]]);
+                    if (this.internalProjection && this.externalProjection) {
+                        geometry.transform(this.externalProjection, 
+                                           this.internalProjection); 
+                    }                       
+                } else {
+                    OpenLayers.Console.error(OpenLayers.i18n(
+                                "unsupportedGeometryType", {'geomType':type}));
+                }
+                // stop looking for different geometry types
+                break;
+            }
+        }
+        
+        // construct feature (optionally with attributes)
+        var attributes;
+        if(this.extractAttributes) {
+            attributes = this.parseAttributes(node);
+        }
+        var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+        
+        feature.gml = {
+            featureType: node.firstChild.nodeName.split(":")[1],
+            featureNS: node.firstChild.namespaceURI,
+            featureNSPrefix: node.firstChild.prefix
+        };
+                
+        // assign fid - this can come from a "fid" or "id" attribute
+        var childNode = node.firstChild;
+        var fid;
+        while(childNode) {
+            if(childNode.nodeType == 1) {
+                fid = childNode.getAttribute("fid") ||
+                      childNode.getAttribute("id");
+                if(fid) {
+                    break;
+                }
+            }
+            childNode = childNode.nextSibling;
+        }
+        feature.fid = fid;
+        return feature;
+    },
+    
+    /**
+     * Property: parseGeometry
+     * Properties of this object are the functions that parse geometries based
+     *     on their type.
+     */
+    parseGeometry: {
+        
+        /**
+         * Method: parseGeometry.point
+         * Given a GML node representing a point geometry, create an OpenLayers
+         *     point geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Point>} A point geometry.
+         */
+        point: function(node) {
+            /**
+             * Three coordinate variations to consider:
+             * 1) <gml:pos>x y z</gml:pos>
+             * 2) <gml:coordinates>x, y, z</gml:coordinates>
+             * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
+             */
+            var nodeList, coordString;
+            var coords = [];
+
+            // look for <gml:pos>
+            var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos");
+            if(nodeList.length > 0) {
+                coordString = nodeList[0].firstChild.nodeValue;
+                coordString = coordString.replace(this.regExes.trimSpace, "");
+                coords = coordString.split(this.regExes.splitSpace);
+            }
+
+            // look for <gml:coordinates>
+            if(coords.length == 0) {
+                nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "coordinates");
+                if(nodeList.length > 0) {
+                    coordString = nodeList[0].firstChild.nodeValue;
+                    coordString = coordString.replace(this.regExes.removeSpace,
+                                                      "");
+                    coords = coordString.split(",");
+                }
+            }
+
+            // look for <gml:coord>
+            if(coords.length == 0) {
+                nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "coord");
+                if(nodeList.length > 0) {
+                    var xList = this.getElementsByTagNameNS(nodeList[0],
+                                                            this.gmlns, "X");
+                    var yList = this.getElementsByTagNameNS(nodeList[0],
+                                                            this.gmlns, "Y");
+                    if(xList.length > 0 && yList.length > 0) {
+                        coords = [xList[0].firstChild.nodeValue,
+                                  yList[0].firstChild.nodeValue];
+                    }
+                }
+            }
+                
+            // preserve third dimension
+            if(coords.length == 2) {
+                coords[2] = null;
+            }
+            
+            if (this.xy) {
+                return new OpenLayers.Geometry.Point(coords[0], coords[1],
+                                                 coords[2]);
+            }
+            else{
+                return new OpenLayers.Geometry.Point(coords[1], coords[0],
+                                                 coords[2]);
+            }
+        },
+        
+        /**
+         * Method: parseGeometry.multipoint
+         * Given a GML node representing a multipoint geometry, create an
+         *     OpenLayers multipoint geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
+         */
+        multipoint: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "Point");
+            var components = [];
+            if(nodeList.length > 0) {
+                var point;
+                for(var i=0; i<nodeList.length; ++i) {
+                    point = this.parseGeometry.point.apply(this, [nodeList[i]]);
+                    if(point) {
+                        components.push(point);
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.MultiPoint(components);
+        },
+        
+        /**
+         * Method: parseGeometry.linestring
+         * Given a GML node representing a linestring geometry, create an
+         *     OpenLayers linestring geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+         */
+        linestring: function(node, ring) {
+            /**
+             * Two coordinate variations to consider:
+             * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
+             * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
+             */
+            var nodeList, coordString;
+            var coords = [];
+            var points = [];
+
+            // look for <gml:posList>
+            nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList");
+            if(nodeList.length > 0) {
+                coordString = this.concatChildValues(nodeList[0]);
+                coordString = coordString.replace(this.regExes.trimSpace, "");
+                coords = coordString.split(this.regExes.splitSpace);
+                var dim = parseInt(nodeList[0].getAttribute("dimension"));
+                var j, x, y, z;
+                for(var i=0; i<coords.length/dim; ++i) {
+                    j = i * dim;
+                    x = coords[j];
+                    y = coords[j+1];
+                    z = (dim == 2) ? null : coords[j+2];
+                    if (this.xy) {
+                        points.push(new OpenLayers.Geometry.Point(x, y, z));
+                    } else {
+                        points.push(new OpenLayers.Geometry.Point(y, x, z));
+                    }
+                }
+            }
+
+            // look for <gml:coordinates>
+            if(coords.length == 0) {
+                nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "coordinates");
+                if(nodeList.length > 0) {
+                    coordString = this.concatChildValues(nodeList[0]);
+                    coordString = coordString.replace(this.regExes.trimSpace,
+                                                      "");
+                    coordString = coordString.replace(this.regExes.trimComma,
+                                                      ",");
+                    var pointList = coordString.split(this.regExes.splitSpace);
+                    for(var i=0; i<pointList.length; ++i) {
+                        coords = pointList[i].split(",");
+                        if(coords.length == 2) {
+                            coords[2] = null;
+                        }
+                        if (this.xy) {
+                            points.push(new OpenLayers.Geometry.Point(coords[0],
+                                                                  coords[1],
+                                                                  coords[2]));
+                        } else {
+                            points.push(new OpenLayers.Geometry.Point(coords[1],
+                                                                  coords[0],
+                                                                  coords[2]));
+                        }
+                    }
+                }
+            }
+
+            var line = null;
+            if(points.length != 0) {
+                if(ring) {
+                    line = new OpenLayers.Geometry.LinearRing(points);
+                } else {
+                    line = new OpenLayers.Geometry.LineString(points);
+                }
+            }
+            return line;
+        },
+        
+        /**
+         * Method: parseGeometry.multilinestring
+         * Given a GML node representing a multilinestring geometry, create an
+         *     OpenLayers multilinestring geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.MultiLineString>} A multilinestring geometry.
+         */
+        multilinestring: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "LineString");
+            var components = [];
+            if(nodeList.length > 0) {
+                var line;
+                for(var i=0; i<nodeList.length; ++i) {
+                    line = this.parseGeometry.linestring.apply(this,
+                                                               [nodeList[i]]);
+                    if(line) {
+                        components.push(line);
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.MultiLineString(components);
+        },
+        
+        /**
+         * Method: parseGeometry.polygon
+         * Given a GML node representing a polygon geometry, create an
+         *     OpenLayers polygon geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+         */
+        polygon: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "LinearRing");
+            var components = [];
+            if(nodeList.length > 0) {
+                // this assumes exterior ring first, inner rings after
+                var ring;
+                for(var i=0; i<nodeList.length; ++i) {
+                    ring = this.parseGeometry.linestring.apply(this,
+                                                        [nodeList[i], true]);
+                    if(ring) {
+                        components.push(ring);
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.Polygon(components);
+        },
+        
+        /**
+         * Method: parseGeometry.multipolygon
+         * Given a GML node representing a multipolygon geometry, create an
+         *     OpenLayers multipolygon geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.MultiPolygon>} A multipolygon geometry.
+         */
+        multipolygon: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "Polygon");
+            var components = [];
+            if(nodeList.length > 0) {
+                var polygon;
+                for(var i=0; i<nodeList.length; ++i) {
+                    polygon = this.parseGeometry.polygon.apply(this,
+                                                               [nodeList[i]]);
+                    if(polygon) {
+                        components.push(polygon);
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.MultiPolygon(components);
+        },
+        
+        envelope: function(node) {
+            var components = [];
+            var coordString;
+            var envelope;
+            
+            var lpoint = this.getElementsByTagNameNS(node, this.gmlns, "lowerCorner");
+            if (lpoint.length > 0) {
+                var coords = [];
+                
+                if(lpoint.length > 0) {
+                    coordString = lpoint[0].firstChild.nodeValue;
+                    coordString = coordString.replace(this.regExes.trimSpace, "");
+                    coords = coordString.split(this.regExes.splitSpace);
+                }
+                
+                if(coords.length == 2) {
+                    coords[2] = null;
+                }
+                if (this.xy) {
+                    var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
+                } else {
+                    var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
+                }
+            }
+            
+            var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner");
+            if (upoint.length > 0) {
+                var coords = [];
+                
+                if(upoint.length > 0) {
+                    coordString = upoint[0].firstChild.nodeValue;
+                    coordString = coordString.replace(this.regExes.trimSpace, "");
+                    coords = coordString.split(this.regExes.splitSpace);
+                }
+                
+                if(coords.length == 2) {
+                    coords[2] = null;
+                }
+                if (this.xy) {
+                    var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
+                } else {
+                    var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
+                }
+            }
+            
+            if (lowerPoint && upperPoint) {
+                components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
+                components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y));
+                components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y));
+                components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y));
+                components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
+                
+                var ring = new OpenLayers.Geometry.LinearRing(components);
+                envelope = new OpenLayers.Geometry.Polygon([ring]);
+            }
+            return envelope; 
+        }
+    },
+    
+    /**
+     * Method: parseAttributes
+     *
+     * Parameters:
+     * node - {<DOMElement>}
+     *
+     * Returns:
+     * {Object} An attributes object.
+     */
+    parseAttributes: function(node) {
+        var attributes = {};
+        // assume attributes are children of the first type 1 child
+        var childNode = node.firstChild;
+        var children, i, child, grandchildren, grandchild, name, value;
+        while(childNode) {
+            if(childNode.nodeType == 1) {
+                // attributes are type 1 children with one type 3 child
+                children = childNode.childNodes;
+                for(i=0; i<children.length; ++i) {
+                    child = children[i];
+                    if(child.nodeType == 1) {
+                        grandchildren = child.childNodes;
+                        if(grandchildren.length == 1) {
+                            grandchild = grandchildren[0];
+                            if(grandchild.nodeType == 3 ||
+                               grandchild.nodeType == 4) {
+                                name = (child.prefix) ?
+                                        child.nodeName.split(":")[1] :
+                                        child.nodeName;
+                                value = grandchild.nodeValue.replace(
+                                                this.regExes.trimSpace, "");
+                                attributes[name] = value;
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+            childNode = childNode.nextSibling;
+        }
+        return attributes;
+    },
+    
+    /**
+     * APIMethod: write
+     * Generate a GML document string given a list of features. 
+     * 
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} List of features to
+     *     serialize into a string.
+     *
+     * Returns:
+     * {String} A string representing the GML document.
+     */
+    write: function(features) {
+        if(!(features instanceof Array)) {
+            features = [features];
+        }
+        var gml = this.createElementNS("http://www.opengis.net/wfs",
+                                       "wfs:" + this.collectionName);
+        for(var i=0; i<features.length; i++) {
+            gml.appendChild(this.createFeatureXML(features[i]));
+        }
+        return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
+    },
+
+    /** 
+     * Method: createFeatureXML
+     * Accept an OpenLayers.Feature.Vector, and build a GML node for it.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML.
+     *
+     * Returns:
+     * {DOMElement} A node reprensting the feature in GML.
+     */
+    createFeatureXML: function(feature) {
+        var geometry = feature.geometry;
+        var geometryNode = this.buildGeometryNode(geometry);
+        var geomContainer = this.createElementNS(this.featureNS,
+                                                 this.featurePrefix + ":" +
+                                                 this.geometryName);
+        geomContainer.appendChild(geometryNode);
+        var featureNode = this.createElementNS(this.gmlns,
+                                               "gml:" + this.featureName);
+        var featureContainer = this.createElementNS(this.featureNS,
+                                                    this.featurePrefix + ":" +
+                                                    this.layerName);
+        var fid = feature.fid || feature.id;
+        featureContainer.setAttribute("fid", fid);
+        featureContainer.appendChild(geomContainer);
+        for(var attr in feature.attributes) {
+            var attrText = this.createTextNode(feature.attributes[attr]); 
+            var nodename = attr.substring(attr.lastIndexOf(":") + 1);
+            var attrContainer = this.createElementNS(this.featureNS,
+                                                     this.featurePrefix + ":" +
+                                                     nodename);
+            attrContainer.appendChild(attrText);
+            featureContainer.appendChild(attrContainer);
+        }    
+        featureNode.appendChild(featureContainer);
+        return featureNode;
+    },
+    
+    /**
+     * APIMethod: buildGeometryNode
+     */
+    buildGeometryNode: function(geometry) {
+        if (this.externalProjection && this.internalProjection) {
+            geometry = geometry.clone();
+            geometry.transform(this.internalProjection, 
+                               this.externalProjection);
+        }    
+        var className = geometry.CLASS_NAME;
+        var type = className.substring(className.lastIndexOf(".") + 1);
+        var builder = this.buildGeometry[type.toLowerCase()];
+        return builder.apply(this, [geometry]);
+    },
+
+    /**
+     * Property: buildGeometry
+     * Object containing methods to do the actual geometry node building
+     *     based on geometry type.
+     */
+    buildGeometry: {
+        // TBD retrieve the srs from layer
+        // srsName is non-standard, so not including it until it's right.
+        // gml.setAttribute("srsName",
+        //                  "http://www.opengis.net/gml/srs/epsg.xml#4326");
+
+        /**
+         * Method: buildGeometry.point
+         * Given an OpenLayers point geometry, create a GML point.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML point node.
+         */
+        point: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:Point");
+            gml.appendChild(this.buildCoordinatesNode(geometry));
+            return gml;
+        },
+        
+        /**
+         * Method: buildGeometry.multipoint
+         * Given an OpenLayers multipoint geometry, create a GML multipoint.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML multipoint node.
+         */
+        multipoint: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:MultiPoint");
+            var points = geometry.components;
+            var pointMember, pointGeom;
+            for(var i=0; i<points.length; i++) { 
+                pointMember = this.createElementNS(this.gmlns,
+                                                   "gml:pointMember");
+                pointGeom = this.buildGeometry.point.apply(this,
+                                                               [points[i]]);
+                pointMember.appendChild(pointGeom);
+                gml.appendChild(pointMember);
+            }
+            return gml;            
+        },
+        
+        /**
+         * Method: buildGeometry.linestring
+         * Given an OpenLayers linestring geometry, create a GML linestring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML linestring node.
+         */
+        linestring: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:LineString");
+            gml.appendChild(this.buildCoordinatesNode(geometry));
+            return gml;
+        },
+        
+        /**
+         * Method: buildGeometry.multilinestring
+         * Given an OpenLayers multilinestring geometry, create a GML
+         *     multilinestring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.MultiLineString>} A multilinestring
+         *     geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML multilinestring node.
+         */
+        multilinestring: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:MultiLineString");
+            var lines = geometry.components;
+            var lineMember, lineGeom;
+            for(var i=0; i<lines.length; ++i) {
+                lineMember = this.createElementNS(this.gmlns,
+                                                  "gml:lineStringMember");
+                lineGeom = this.buildGeometry.linestring.apply(this,
+                                                                   [lines[i]]);
+                lineMember.appendChild(lineGeom);
+                gml.appendChild(lineMember);
+            }
+            return gml;
+        },
+        
+        /**
+         * Method: buildGeometry.linearring
+         * Given an OpenLayers linearring geometry, create a GML linearring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML linearring node.
+         */
+        linearring: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:LinearRing");
+            gml.appendChild(this.buildCoordinatesNode(geometry));
+            return gml;
+        },
+        
+        /**
+         * Method: buildGeometry.polygon
+         * Given an OpenLayers polygon geometry, create a GML polygon.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML polygon node.
+         */
+        polygon: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:Polygon");
+            var rings = geometry.components;
+            var ringMember, ringGeom, type;
+            for(var i=0; i<rings.length; ++i) {
+                type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+                ringMember = this.createElementNS(this.gmlns,
+                                                  "gml:" + type);
+                ringGeom = this.buildGeometry.linearring.apply(this,
+                                                                   [rings[i]]);
+                ringMember.appendChild(ringGeom);
+                gml.appendChild(ringMember);
+            }
+            return gml;
+        },
+        
+        /**
+         * Method: buildGeometry.multipolygon
+         * Given an OpenLayers multipolygon geometry, create a GML multipolygon.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.MultiPolygon>} A multipolygon
+         *     geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML multipolygon node.
+         */
+        multipolygon: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon");
+            var polys = geometry.components;
+            var polyMember, polyGeom;
+            for(var i=0; i<polys.length; ++i) {
+                polyMember = this.createElementNS(this.gmlns,
+                                                  "gml:polygonMember");
+                polyGeom = this.buildGeometry.polygon.apply(this,
+                                                                [polys[i]]);
+                polyMember.appendChild(polyGeom);
+                gml.appendChild(polyMember);
+            }
+            return gml;
+        }
+    },
+
+    /**
+     * Method: buildCoordinates
+     * builds the coordinates XmlNode
+     * (code)
+     * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
+     * (end)
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>} 
+     *
+     * Returns:
+     * {XmlNode} created xmlNode
+     */
+    buildCoordinatesNode: function(geometry) {
+        var coordinatesNode = this.createElementNS(this.gmlns,
+                                                   "gml:coordinates");
+        coordinatesNode.setAttribute("decimal", ".");
+        coordinatesNode.setAttribute("cs", ",");
+        coordinatesNode.setAttribute("ts", " ");
+        
+        var points = (geometry.components) ? geometry.components : [geometry];
+        var parts = [];
+        for(var i=0; i<points.length; i++) {
+            parts.push(points[i].x + "," + points[i].y);
+        }
+
+        var txtNode = this.createTextNode(parts.join(" "));
+        coordinatesNode.appendChild(txtNode);
+        
+        return coordinatesNode;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.GML" 
+});
+/* ======================================================================
+    OpenLayers/Format/GPX.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ *
+ * Class: OpenLayers.Format.GPX
+ * Read/write GPX parser. Create a new instance with the 
+ *     <OpenLayers.Format.GPX> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GPX = OpenLayers.Class(OpenLayers.Format.XML, {
+   /**
+    * APIProperty: extractWaypoints
+    * {Boolean} Extract waypoints from GPX. (default: true)
+    */
+    extractWaypoints: true,
+    
+   /**
+    * APIProperty: extractTracks
+    * {Boolean} Extract tracks from GPX. (default: true)
+    */
+    extractTracks: true,
+    
+   /**
+    * APIProperty: extractRoutes
+    * {Boolean} Extract routes from GPX. (default: true)
+    */
+    extractRoutes: true,
+    
+    /**
+     * APIProperty: extractAttributes
+     * {Boolean} Extract feature attributes from GPX. (default: true)
+     *     NOTE: Attributes as part of extensions to the GPX standard may not
+     *     be extracted.
+     */
+    extractAttributes: true,
+    
+    /**
+     * Constructor: OpenLayers.Format.GPX
+     * Create a new parser for GPX.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+    
+    /**
+     * APIMethod: read
+     * Return a list of features from a GPX doc
+     *
+     * Parameters:
+     * doc - {Element} 
+     *
+     * Returns:
+     * An Array of <OpenLayers.Feature.Vector>s
+     */
+    read: function(doc) {
+        if (typeof doc == "string") { 
+            doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+        }
+        var features = [];
+        
+        if(this.extractWaypoints) {
+            var waypoints = doc.getElementsByTagName("wpt");
+            for (var l = 0, len = waypoints.length; l < len; l++) {
+                var attrs = {};
+                if(this.extractAttributes) {
+                    attrs = this.parseAttributes(waypoints[l]);
+                }
+                var wpt = new OpenLayers.Geometry.Point(waypoints[l].getAttribute("lon"), waypoints[l].getAttribute("lat"));
+                features.push(new OpenLayers.Feature.Vector(wpt, attrs));
+            }
+        }
+        
+        if(this.extractTracks) {
+            var tracks = doc.getElementsByTagName("trk");
+            for (var i=0, len=tracks.length; i<len; i++) {
+                // Attributes are only in trk nodes, not trkseg nodes
+                var attrs = {}
+                if(this.extractAttributes) {
+                    attrs = this.parseAttributes(tracks[i]);
+                }
+                
+                var segs = this.getElementsByTagNameNS(tracks[i], tracks[i].namespaceURI, "trkseg");
+                for (var j = 0, seglen = segs.length; j < seglen; j++) {
+                    // We don't yet support extraction of trkpt attributes
+                    // All trksegs of a trk get that trk's attributes
+                    var track = this.extractSegment(segs[j], "trkpt");
+                    features.push(new OpenLayers.Feature.Vector(track, attrs));
+                }
+            }
+        }
+        
+        if(this.extractRoutes) {
+            var routes = doc.getElementsByTagName("rte");
+            for (var k=0, klen=routes.length; k<klen; k++) {
+                var attrs = {}
+                if(this.extractAttributes) {
+                    attrs = this.parseAttributes(routes[k]);
+                }
+                var route = this.extractSegment(routes[k], "rtept");
+                features.push(new OpenLayers.Feature.Vector(route, attrs));
+            }
+        }
+        
+        if (this.internalProjection && this.externalProjection) {
+            for (var g = 0, featLength = features.length; g < featLength; g++) {
+                features[g].geometry.transform(this.externalProjection,
+                                    this.internalProjection);
+            }
+        }
+        
+        return features;
+    },
+    
+   /**
+    * Method: extractSegment
+    *
+    * Parameters:
+    * segment - {<DOMElement>} a trkseg or rte node to parse
+    * segmentType - {String} nodeName of waypoints that form the line
+    *
+    * Returns:
+    * {<OpenLayers.Geometry.LineString>} A linestring geometry
+    */
+    extractSegment: function(segment, segmentType) {
+        var points = this.getElementsByTagNameNS(segment, segment.namespaceURI, segmentType);
+        var point_features = [];
+        for (var i = 0, len = points.length; i < len; i++) {
+            point_features.push(new OpenLayers.Geometry.Point(points[i].getAttribute("lon"), points[i].getAttribute("lat")));
+        }
+        return new OpenLayers.Geometry.LineString(point_features);
+    },
+    
+    /**
+     * Method: parseAttributes
+     *
+     * Parameters:
+     * node - {<DOMElement>}
+     *
+     * Returns:
+     * {Object} An attributes object.
+     */
+    parseAttributes: function(node) {
+        // node is either a wpt, trk or rte
+        // attributes are children of the form <attr>value</attr>
+        var attributes = {};
+        var attrNode = node.firstChild;
+        while(attrNode) {
+            if(attrNode.nodeType == 1) {
+                var value = attrNode.firstChild;
+                if(value.nodeType == 3 || value.nodeType == 4) {
+                    name = (attrNode.prefix) ?
+                        attrNode.nodeName.split(":")[1] :
+                        attrNode.nodeName;
+                    attributes[name] = value.nodeValue;
+                }
+            }
+            attrNode = attrNode.nextSibling;
+        }
+        return attributes;
+    },
+    
+    CLASS_NAME: "OpenLayers.Format.GPX"
+});
+/* ======================================================================
+    OpenLayers/Format/GeoJSON.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/JSON.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GeoJSON
+ * Read and write GeoJSON. Create a new parser with the
+ *     <OpenLayers.Format.GeoJSON> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.JSON>
+ */
+OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, {
+
+    /**
+     * Constructor: OpenLayers.Format.GeoJSON
+     * Create a new parser for GeoJSON.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.JSON.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: read
+     * Deserialize a GeoJSON string.
+     *
+     * Parameters:
+     * json - {String} A GeoJSON string
+     * type - {String} Optional string that determines the structure of
+     *     the output.  Supported values are "Geometry", "Feature", and
+     *     "FeatureCollection".  If absent or null, a default of
+     *     "FeatureCollection" is assumed.
+     * filter - {Function} A function which will be called for every key and
+     *     value at every level of the final result. Each value will be
+     *     replaced by the result of the filter function. This can be used to
+     *     reform generic objects into instances of classes, or to transform
+     *     date strings into Date objects.
+     *
+     * Returns: 
+     * {Object} The return depends on the value of the type argument. If type
+     *     is "FeatureCollection" (the default), the return will be an array
+     *     of <OpenLayers.Feature.Vector>. If type is "Geometry", the input json
+     *     must represent a single geometry, and the return will be an
+     *     <OpenLayers.Geometry>.  If type is "Feature", the input json must
+     *     represent a single feature, and the return will be an
+     *     <OpenLayers.Feature.Vector>.
+     */
+    read: function(json, type, filter) {
+        type = (type) ? type : "FeatureCollection";
+        var results = null;
+        var obj = null;
+        if (typeof json == "string") {
+            obj = OpenLayers.Format.JSON.prototype.read.apply(this,
+                                                              [json, filter]);
+        } else { 
+            obj = json;
+        }    
+        if(!obj) {
+            OpenLayers.Console.error("Bad JSON: " + json);
+        } else if(typeof(obj.type) != "string") {
+            OpenLayers.Console.error("Bad GeoJSON - no type: " + json);
+        } else if(this.isValidType(obj, type)) {
+            switch(type) {
+                case "Geometry":
+                    try {
+                        results = this.parseGeometry(obj);
+                    } catch(err) {
+                        OpenLayers.Console.error(err);
+                    }
+                    break;
+                case "Feature":
+                    try {
+                        results = this.parseFeature(obj);
+                        results.type = "Feature";
+                    } catch(err) {
+                        OpenLayers.Console.error(err);
+                    }
+                    break;
+                case "FeatureCollection":
+                    // for type FeatureCollection, we allow input to be any type
+                    results = [];
+                    switch(obj.type) {
+                        case "Feature":
+                            try {
+                                results.push(this.parseFeature(obj));
+                            } catch(err) {
+                                results = null;
+                                OpenLayers.Console.error(err);
+                            }
+                            break;
+                        case "FeatureCollection":
+                            for(var i=0, len=obj.features.length; i<len; ++i) {
+                                try {
+                                    results.push(this.parseFeature(obj.features[i]));
+                                } catch(err) {
+                                    results = null;
+                                    OpenLayers.Console.error(err);
+                                }
+                            }
+                            break;
+                        default:
+                            try {
+                                var geom = this.parseGeometry(obj);
+                                results.push(new OpenLayers.Feature.Vector(geom));
+                            } catch(err) {
+                                results = null;
+                                OpenLayers.Console.error(err);
+                            }
+                    }
+                break;
+            }
+        }
+        return results;
+    },
+    
+    /**
+     * Method: isValidType
+     * Check if a GeoJSON object is a valid representative of the given type.
+     *
+     * Returns:
+     * {Boolean} The object is valid GeoJSON object of the given type.
+     */
+    isValidType: function(obj, type) {
+        var valid = false;
+        switch(type) {
+            case "Geometry":
+                if(OpenLayers.Util.indexOf(
+                    ["Point", "MultiPoint", "LineString", "MultiLineString",
+                     "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
+                    obj.type) == -1) {
+                    // unsupported geometry type
+                    OpenLayers.Console.error("Unsupported geometry type: " +
+                                              obj.type);
+                } else {
+                    valid = true;
+                }
+                break;
+            case "FeatureCollection":
+                // allow for any type to be converted to a feature collection
+                valid = true;
+                break;
+            default:
+                // for Feature types must match
+                if(obj.type == type) {
+                    valid = true;
+                } else {
+                    OpenLayers.Console.error("Cannot convert types from " +
+                                              obj.type + " to " + type);
+                }
+        }
+        return valid;
+    },
+    
+    /**
+     * Method: parseFeature
+     * Convert a feature object from GeoJSON into an
+     *     <OpenLayers.Feature.Vector>.
+     *
+     * Parameters:
+     * obj - {Object} An object created from a GeoJSON object
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A feature.
+     */
+    parseFeature: function(obj) {
+        var feature, geometry, attributes;
+        attributes = (obj.properties) ? obj.properties : {};
+        try {
+            geometry = this.parseGeometry(obj.geometry);            
+        } catch(err) {
+            // deal with bad geometries
+            throw err;
+        }
+        feature = new OpenLayers.Feature.Vector(geometry, attributes);
+        if(obj.id) {
+            feature.fid = obj.id;
+        }
+        return feature;
+    },
+    
+    /**
+     * Method: parseGeometry
+     * Convert a geometry object from GeoJSON into an <OpenLayers.Geometry>.
+     *
+     * Parameters:
+     * obj - {Object} An object created from a GeoJSON object
+     *
+     * Returns: 
+     * {<OpenLayers.Geometry>} A geometry.
+     */
+    parseGeometry: function(obj) {
+        if (obj == null) {
+            return null;
+        }
+        var geometry;
+        if(obj.type == "GeometryCollection") {
+            if(!(obj.geometries instanceof Array)) {
+                throw "GeometryCollection must have geometries array: " + obj;
+            }
+            var numGeom = obj.geometries.length;
+            var components = new Array(numGeom);
+            for(var i=0; i<numGeom; ++i) {
+                components[i] = this.parseGeometry.apply(
+                    this, [obj.geometries[i]]
+                );
+            }
+            geometry = new OpenLayers.Geometry.Collection(components);
+        } else {
+            if(!(obj.coordinates instanceof Array)) {
+                throw "Geometry must have coordinates array: " + obj;
+            }
+            if(!this.parseCoords[obj.type.toLowerCase()]) {
+                throw "Unsupported geometry type: " + obj.type;
+            }
+            try {
+                geometry = this.parseCoords[obj.type.toLowerCase()].apply(
+                    this, [obj.coordinates]
+                );
+            } catch(err) {
+                // deal with bad coordinates
+                throw err;
+            }
+        }
+        if (this.internalProjection && this.externalProjection) {
+            geometry.transform(this.externalProjection, 
+                               this.internalProjection); 
+        }                       
+        return geometry;
+    },
+    
+    /**
+     * Property: parseCoords
+     * Object with properties corresponding to the GeoJSON geometry types.
+     *     Property values are functions that do the actual parsing.
+     */
+    parseCoords: {
+        /**
+         * Method: parseCoords.point
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array - {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "point": function(array) {
+            if(array.length != 2) {
+                throw "Only 2D points are supported: " + array;
+            }
+            return new OpenLayers.Geometry.Point(array[0], array[1]);
+        },
+        
+        /**
+         * Method: parseCoords.multipoint
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "multipoint": function(array) {
+            var points = [];
+            var p = null;
+            for(var i=0, len=array.length; i<len; ++i) {
+                try {
+                    p = this.parseCoords["point"].apply(this, [array[i]]);
+                } catch(err) {
+                    throw err;
+                }
+                points.push(p);
+            }
+            return new OpenLayers.Geometry.MultiPoint(points);
+        },
+
+        /**
+         * Method: parseCoords.linestring
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array - {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "linestring": function(array) {
+            var points = [];
+            var p = null;
+            for(var i=0, len=array.length; i<len; ++i) {
+                try {
+                    p = this.parseCoords["point"].apply(this, [array[i]]);
+                } catch(err) {
+                    throw err;
+                }
+                points.push(p);
+            }
+            return new OpenLayers.Geometry.LineString(points);
+        },
+        
+        /**
+         * Method: parseCoords.multilinestring
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array - {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "multilinestring": function(array) {
+            var lines = [];
+            var l = null;
+            for(var i=0, len=array.length; i<len; ++i) {
+                try {
+                    l = this.parseCoords["linestring"].apply(this, [array[i]]);
+                } catch(err) {
+                    throw err;
+                }
+                lines.push(l);
+            }
+            return new OpenLayers.Geometry.MultiLineString(lines);
+        },
+        
+        /**
+         * Method: parseCoords.polygon
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "polygon": function(array) {
+            var rings = [];
+            var r, l;
+            for(var i=0, len=array.length; i<len; ++i) {
+                try {
+                    l = this.parseCoords["linestring"].apply(this, [array[i]]);
+                } catch(err) {
+                    throw err;
+                }
+                r = new OpenLayers.Geometry.LinearRing(l.components);
+                rings.push(r);
+            }
+            return new OpenLayers.Geometry.Polygon(rings);
+        },
+
+        /**
+         * Method: parseCoords.multipolygon
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array - {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "multipolygon": function(array) {
+            var polys = [];
+            var p = null;
+            for(var i=0, len=array.length; i<len; ++i) {
+                try {
+                    p = this.parseCoords["polygon"].apply(this, [array[i]]);
+                } catch(err) {
+                    throw err;
+                }
+                polys.push(p);
+            }
+            return new OpenLayers.Geometry.MultiPolygon(polys);
+        },
+
+        /**
+         * Method: parseCoords.box
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array - {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "box": function(array) {
+            if(array.length != 2) {
+                throw "GeoJSON box coordinates must have 2 elements";
+            }
+            return new OpenLayers.Geometry.Polygon([
+                new OpenLayers.Geometry.LinearRing([
+                    new OpenLayers.Geometry.Point(array[0][0], array[0][1]),
+                    new OpenLayers.Geometry.Point(array[1][0], array[0][1]),
+                    new OpenLayers.Geometry.Point(array[1][0], array[1][1]),
+                    new OpenLayers.Geometry.Point(array[0][0], array[1][1]),
+                    new OpenLayers.Geometry.Point(array[0][0], array[0][1])
+                ])
+            ]);
+        }
+
+    },
+
+    /**
+     * APIMethod: write
+     * Serialize a feature, geometry, array of features into a GeoJSON string.
+     *
+     * Parameters:
+     * obj - {Object} An <OpenLayers.Feature.Vector>, <OpenLayers.Geometry>,
+     *     or an array of features.
+     * pretty - {Boolean} Structure the output with newlines and indentation.
+     *     Default is false.
+     *
+     * Returns:
+     * {String} The GeoJSON string representation of the input geometry,
+     *     features, or array of features.
+     */
+    write: function(obj, pretty) {
+        var geojson = {
+            "type": null
+        };
+        if(obj instanceof Array) {
+            geojson.type = "FeatureCollection";
+            var numFeatures = obj.length;
+            geojson.features = new Array(numFeatures);
+            for(var i=0; i<numFeatures; ++i) {
+                var element = obj[i];
+                if(!element instanceof OpenLayers.Feature.Vector) {
+                    var msg = "FeatureCollection only supports collections " +
+                              "of features: " + element;
+                    throw msg;
+                }
+                geojson.features[i] = this.extract.feature.apply(
+                    this, [element]
+                );
+            }
+        } else if (obj.CLASS_NAME.indexOf("OpenLayers.Geometry") == 0) {
+            geojson = this.extract.geometry.apply(this, [obj]);
+        } else if (obj instanceof OpenLayers.Feature.Vector) {
+            geojson = this.extract.feature.apply(this, [obj]);
+            if(obj.layer && obj.layer.projection) {
+                geojson.crs = this.createCRSObject(obj);
+            }
+        }
+        return OpenLayers.Format.JSON.prototype.write.apply(this,
+                                                            [geojson, pretty]);
+    },
+
+    /**
+     * Method: createCRSObject
+     * Create the CRS object for an object.
+     *
+     * Parameters:
+     * object - {<OpenLayers.Feature.Vector>} 
+     *
+     * Returns:
+     * {Object} An object which can be assigned to the crs property
+     * of a GeoJSON object.
+     */
+    createCRSObject: function(object) {
+       var proj = object.layer.projection.toString();
+       var crs = {};
+       if (proj.match(/epsg:/i)) {
+           var code = parseInt(proj.substring(proj.indexOf(":") + 1));
+           if (code == 4326) {
+               crs = {
+                   "type": "OGC",
+                   "properties": {
+                       "urn": "urn:ogc:def:crs:OGC:1.3:CRS84"
+                   }
+               };
+           } else {    
+               crs = {
+                   "type": "EPSG",
+                   "properties": {
+                       "code": code 
+                   }
+               };
+           }    
+       }
+       return crs;
+    },
+    
+    /**
+     * Property: extract
+     * Object with properties corresponding to the GeoJSON types.
+     *     Property values are functions that do the actual value extraction.
+     */
+    extract: {
+        /**
+         * Method: extract.feature
+         * Return a partial GeoJSON object representing a single feature.
+         *
+         * Parameters:
+         * feature - {<OpenLayers.Feature.Vector>}
+         *
+         * Returns:
+         * {Object} An object representing the point.
+         */
+        'feature': function(feature) {
+            var geom = this.extract.geometry.apply(this, [feature.geometry]);
+            return {
+                "type": "Feature",
+                "id": feature.fid == null ? feature.id : feature.fid,
+                "properties": feature.attributes,
+                "geometry": geom
+            };
+        },
+        
+        /**
+         * Method: extract.geometry
+         * Return a GeoJSON object representing a single geometry.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry>}
+         *
+         * Returns:
+         * {Object} An object representing the geometry.
+         */
+        'geometry': function(geometry) {
+            if (geometry == null) {
+                return null;
+            }
+            if (this.internalProjection && this.externalProjection) {
+                geometry = geometry.clone();
+                geometry.transform(this.internalProjection, 
+                                   this.externalProjection);
+            }                       
+            var geometryType = geometry.CLASS_NAME.split('.')[2];
+            var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
+            var json;
+            if(geometryType == "Collection") {
+                json = {
+                    "type": "GeometryCollection",
+                    "geometries": data
+                };
+            } else {
+                json = {
+                    "type": geometryType,
+                    "coordinates": data
+                };
+            }
+            
+            return json;
+        },
+
+        /**
+         * Method: extract.point
+         * Return an array of coordinates from a point.
+         *
+         * Parameters:
+         * point - {<OpenLayers.Geometry.Point>}
+         *
+         * Returns: 
+         * {Array} An array of coordinates representing the point.
+         */
+        'point': function(point) {
+            return [point.x, point.y];
+        },
+
+        /**
+         * Method: extract.multipoint
+         * Return an array of point coordinates from a multipoint.
+         *
+         * Parameters:
+         * multipoint - {<OpenLayers.Geometry.MultiPoint>}
+         *
+         * Returns:
+         * {Array} An array of point coordinate arrays representing
+         *     the multipoint.
+         */
+        'multipoint': function(multipoint) {
+            var array = [];
+            for(var i=0, len=multipoint.components.length; i<len; ++i) {
+                array.push(this.extract.point.apply(this, [multipoint.components[i]]));
+            }
+            return array;
+        },
+        
+        /**
+         * Method: extract.linestring
+         * Return an array of coordinate arrays from a linestring.
+         *
+         * Parameters:
+         * linestring - {<OpenLayers.Geometry.LineString>}
+         *
+         * Returns:
+         * {Array} An array of coordinate arrays representing
+         *     the linestring.
+         */
+        'linestring': function(linestring) {
+            var array = [];
+            for(var i=0, len=linestring.components.length; i<len; ++i) {
+                array.push(this.extract.point.apply(this, [linestring.components[i]]));
+            }
+            return array;
+        },
+
+        /**
+         * Method: extract.multilinestring
+         * Return an array of linestring arrays from a linestring.
+         * 
+         * Parameters:
+         * linestring - {<OpenLayers.Geometry.MultiLineString>}
+         * 
+         * Returns:
+         * {Array} An array of linestring arrays representing
+         *     the multilinestring.
+         */
+        'multilinestring': function(multilinestring) {
+            var array = [];
+            for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+                array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
+            }
+            return array;
+        },
+        
+        /**
+         * Method: extract.polygon
+         * Return an array of linear ring arrays from a polygon.
+         *
+         * Parameters:
+         * polygon - {<OpenLayers.Geometry.Polygon>}
+         * 
+         * Returns:
+         * {Array} An array of linear ring arrays representing the polygon.
+         */
+        'polygon': function(polygon) {
+            var array = [];
+            for(var i=0, len=polygon.components.length; i<len; ++i) {
+                array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
+            }
+            return array;
+        },
+
+        /**
+         * Method: extract.multipolygon
+         * Return an array of polygon arrays from a multipolygon.
+         * 
+         * Parameters:
+         * multipolygon - {<OpenLayers.Geometry.MultiPolygon>}
+         * 
+         * Returns:
+         * {Array} An array of polygon arrays representing
+         *     the multipolygon
+         */
+        'multipolygon': function(multipolygon) {
+            var array = [];
+            for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+                array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
+            }
+            return array;
+        },
+        
+        /**
+         * Method: extract.collection
+         * Return an array of geometries from a geometry collection.
+         * 
+         * Parameters:
+         * collection - {<OpenLayers.Geometry.Collection>}
+         * 
+         * Returns:
+         * {Array} An array of geometry objects representing the geometry
+         *     collection.
+         */
+        'collection': function(collection) {
+            var len = collection.components.length;
+            var array = new Array(len);
+            for(var i=0; i<len; ++i) {
+                array[i] = this.extract.geometry.apply(
+                    this, [collection.components[i]]
+                );
+            }
+            return array;
+        }
+        
+
+    },
+
+    CLASS_NAME: "OpenLayers.Format.GeoJSON" 
+
+});     
+/* ======================================================================
+    OpenLayers/Format/GeoRSS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GeoRSS
+ * Read/write GeoRSS parser. Create a new instance with the 
+ *     <OpenLayers.Format.GeoRSS> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GeoRSS = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * APIProperty: rssns
+     * {String} RSS namespace to use. Defaults to
+     *   "http://backend.userland.com/rss2"
+     */
+    rssns: "http://backend.userland.com/rss2",
+    
+    /**
+     * APIProperty: featurens
+     * {String} Feature Attributes namespace.  Defaults to
+     *    "http://mapserver.gis.umn.edu/mapserver"
+     */
+    featureNS: "http://mapserver.gis.umn.edu/mapserver",
+    
+    /**
+     * APIProperty: georssns
+     * {String} GeoRSS namespace to use.  Defaults to
+     *     "http://www.georss.org/georss"
+     */
+    georssns: "http://www.georss.org/georss",
+
+    /**
+     * APIProperty: geons
+     * {String} W3C Geo namespace to use.  Defaults to
+     *     "http://www.w3.org/2003/01/geo/wgs84_pos#"
+     */
+    geons: "http://www.w3.org/2003/01/geo/wgs84_pos#",
+    
+    /**
+     * APIProperty: featureTitle
+     * {String} Default title for features.  Defaults to "Untitled"
+     */
+    featureTitle: "Untitled",
+    
+    /**
+     * APIProperty: featureDescription
+     * {String} Default description for features.  Defaults to "No Description"
+     */
+    featureDescription: "No Description",
+    
+    /**
+     * Property: gmlParse
+     * {Object} GML Format object for parsing features
+     * Non-API and only created if necessary
+     */
+    gmlParser: null,
+
+    /**
+     * APIProperty: xy
+     * {Boolean} Order of the GML coordinate: true:(x,y) or false:(y,x)
+     * For GeoRSS the default is (y,x), therefore: false
+     */ 
+    xy: false,
+    
+    /**
+     * Constructor: OpenLayers.Format.GeoRSS
+     * Create a new parser for GeoRSS.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+    
+    /**
+     * Method: createGeometryFromItem
+     * Return a geometry from a GeoRSS Item.
+     *
+     * Parameters:
+     * item - {DOMElement} A GeoRSS item node.
+     *
+     * Returns:
+     * {<OpenLayers.Geometry>} A geometry representing the node.
+     */
+    createGeometryFromItem: function(item) {
+        var point = this.getElementsByTagNameNS(item, this.georssns, "point");
+        var lat = this.getElementsByTagNameNS(item, this.geons, 'lat');
+        var lon = this.getElementsByTagNameNS(item, this.geons, 'long');
+        
+        var line = this.getElementsByTagNameNS(item,
+                                                this.georssns,
+                                                "line");
+        var polygon = this.getElementsByTagNameNS(item,
+                                                this.georssns,
+                                                "polygon");
+        var where = this.getElementsByTagNameNS(item, 
+                                                this.georssns, 
+                                                "where");
+        if (point.length > 0 || (lat.length > 0 && lon.length > 0)) {
+            var location;
+            if (point.length > 0) {
+                location = OpenLayers.String.trim(
+                                point[0].firstChild.nodeValue).split(/\s+/);
+                if (location.length !=2) {
+                    location = OpenLayers.String.trim(
+                                point[0].firstChild.nodeValue).split(/\s*,\s*/);
+                }
+            } else {
+                location = [parseFloat(lat[0].firstChild.nodeValue),
+                                parseFloat(lon[0].firstChild.nodeValue)];
+            }    
+
+            var geometry = new OpenLayers.Geometry.Point(parseFloat(location[1]),
+                                                         parseFloat(location[0]));
+              
+        } else if (line.length > 0) {
+            var coords = OpenLayers.String.trim(this.concatChildValues(line[0])).split(/\s+/);
+            var components = []; 
+            var point;
+            for (var i=0, len=coords.length; i<len; i+=2) {
+                point = new OpenLayers.Geometry.Point(parseFloat(coords[i+1]), 
+                                                     parseFloat(coords[i]));
+                components.push(point);
+            }
+            geometry = new OpenLayers.Geometry.LineString(components);
+        } else if (polygon.length > 0) { 
+            var coords = OpenLayers.String.trim(this.concatChildValues(polygon[0])).split(/\s+/);
+            var components = []; 
+            var point;
+            for (var i=0, len=coords.length; i<len; i+=2) {
+                point = new OpenLayers.Geometry.Point(parseFloat(coords[i+1]), 
+                                                     parseFloat(coords[i]));
+                components.push(point);
+            }
+            geometry = new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);
+        } else if (where.length > 0) { 
+            if (!this.gmlParser) {
+              this.gmlParser = new OpenLayers.Format.GML({'xy': this.xy});
+            }
+            var feature = this.gmlParser.parseFeature(where[0]);
+            geometry = feature.geometry;
+        }
+        
+        if (geometry && this.internalProjection && this.externalProjection) {
+            geometry.transform(this.externalProjection, 
+                               this.internalProjection);
+        }
+
+        return geometry;
+    },        
+
+    /**
+     * Method: createFeatureFromItem
+     * Return a feature from a GeoRSS Item.
+     *
+     * Parameters:
+     * item - {DOMElement} A GeoRSS item node.
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A feature representing the item.
+     */
+    createFeatureFromItem: function(item) {
+        var geometry = this.createGeometryFromItem(item);
+     
+        /* Provide defaults for title and description */
+        var title = this.getChildValue(item, "*", "title", this.featureTitle);
+       
+        /* First try RSS descriptions, then Atom summaries */
+        var description = this.getChildValue(
+            item, "*", "description",
+            this.getChildValue(item, "*", "content", this.featureDescription)
+        );
+
+        /* If no link URL is found in the first child node, try the
+           href attribute */
+        var link = this.getChildValue(item, "*", "link");
+        if(!link) {
+            try {
+                link = this.getElementsByTagNameNS(item, "*", "link")[0].getAttribute("href");
+            } catch(e) {
+                link = null;
+            }
+        }
+
+        var id = this.getChildValue(item, "*", "id", null);
+        
+        var data = {
+            "title": title,
+            "description": description,
+            "link": link
+        };
+        var feature = new OpenLayers.Feature.Vector(geometry, data);
+        feature.fid = id;
+        return feature;
+    },        
+    
+    /**
+     * Method: getChildValue
+     *
+     * Parameters:
+     * node - {DOMElement}
+     * nsuri - {String} Child node namespace uri ("*" for any).
+     * name - {String} Child node name.
+     * def - {String} Optional string default to return if no child found.
+     *
+     * Returns:
+     * {String} The value of the first child with the given tag name.  Returns
+     *     default value or empty string if none found.
+     */
+    getChildValue: function(node, nsuri, name, def) {
+        var value;
+        var eles = this.getElementsByTagNameNS(node, nsuri, name);
+        if(eles && eles[0] && eles[0].firstChild
+            && eles[0].firstChild.nodeValue) {
+            value = eles[0].firstChild.nodeValue;
+        } else {
+            value = (def == undefined) ? "" : def;
+        }
+        return value;
+    },
+    
+    /**
+     * APIMethod: read
+     * Return a list of features from a GeoRSS doc
+     
+     * Parameters:
+     * data - {Element} 
+     *
+     * Returns:
+     * An Array of <OpenLayers.Feature.Vector>s
+     */
+    read: function(doc) {
+        if (typeof doc == "string") { 
+            doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+        }
+
+        /* Try RSS items first, then Atom entries */
+        var itemlist = null;
+        itemlist = this.getElementsByTagNameNS(doc, '*', 'item');
+        if (itemlist.length == 0) {
+            itemlist = this.getElementsByTagNameNS(doc, '*', 'entry');
+        }
+        
+        var numItems = itemlist.length;
+        var features = new Array(numItems);
+        for(var i=0; i<numItems; i++) {
+            features[i] = this.createFeatureFromItem(itemlist[i]);
+        }
+        return features;
+    },
+    
+
+    /**
+     * APIMethod: write
+     * Accept Feature Collection, and return a string. 
+     * 
+     * Parameters: 
+     * features - {Array(<OpenLayers.Feature.Vector>)} List of features to serialize into a string.
+     */
+    write: function(features) {
+        var georss;
+        if(features instanceof Array) {
+            georss = this.createElementNS(this.rssns, "rss");
+            for(var i=0, len=features.length; i<len; i++) {
+                georss.appendChild(this.createFeatureXML(features[i]));
+            }
+        } else {
+            georss = this.createFeatureXML(features);
+        }
+        return OpenLayers.Format.XML.prototype.write.apply(this, [georss]);
+    },
+
+    /**
+     * Method: createFeatureXML
+     * Accept an <OpenLayers.Feature.Vector>, and build a geometry for it.
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     *
+     * Returns:
+     * {DOMElement}
+     */
+    createFeatureXML: function(feature) {
+        var geometryNode = this.buildGeometryNode(feature.geometry);
+        var featureNode = this.createElementNS(this.rssns, "item");
+        var titleNode = this.createElementNS(this.rssns, "title");
+        titleNode.appendChild(this.createTextNode(feature.attributes.title ? feature.attributes.title : ""));
+        var descNode = this.createElementNS(this.rssns, "description");
+        descNode.appendChild(this.createTextNode(feature.attributes.description ? feature.attributes.description : ""));
+        featureNode.appendChild(titleNode);
+        featureNode.appendChild(descNode);
+        if (feature.attributes.link) {
+            var linkNode = this.createElementNS(this.rssns, "link");
+            linkNode.appendChild(this.createTextNode(feature.attributes.link));
+            featureNode.appendChild(linkNode);
+        }    
+        for(var attr in feature.attributes) {
+            if (attr == "link" || attr == "title" || attr == "description") { continue; } 
+            var attrText = this.createTextNode(feature.attributes[attr]); 
+            var nodename = attr;
+            if (attr.search(":") != -1) {
+                nodename = attr.split(":")[1];
+            }    
+            var attrContainer = this.createElementNS(this.featureNS, "feature:"+nodename);
+            attrContainer.appendChild(attrText);
+            featureNode.appendChild(attrContainer);
+        }    
+        featureNode.appendChild(geometryNode);
+        return featureNode;
+    },    
+    
+    /** 
+     * Method: buildGeometryNode
+     * builds a GeoRSS node with a given geometry
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     *
+     * Returns:
+     * {DOMElement} A gml node.
+     */
+    buildGeometryNode: function(geometry) {
+        if (this.internalProjection && this.externalProjection) {
+            geometry = geometry.clone();
+            geometry.transform(this.internalProjection, 
+                               this.externalProjection);
+        }
+        var node;
+        // match Polygon
+        if (geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+            node = this.createElementNS(this.georssns, 'georss:polygon');
+            
+            node.appendChild(this.buildCoordinatesNode(geometry.components[0]));
+        }
+        // match LineString
+        else if (geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
+            node = this.createElementNS(this.georssns, 'georss:line');
+            
+            node.appendChild(this.buildCoordinatesNode(geometry));
+        }
+        // match Point
+        else if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+            node = this.createElementNS(this.georssns, 'georss:point');
+            node.appendChild(this.buildCoordinatesNode(geometry));
+        } else {
+            throw "Couldn't parse " + geometry.CLASS_NAME;
+        }  
+        return node;         
+    },
+    
+    /** 
+     * Method: buildCoordinatesNode
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     */
+    buildCoordinatesNode: function(geometry) {
+        var points = null;
+        
+        if (geometry.components) {
+            points = geometry.components;
+        }
+
+        var path;
+        if (points) {
+            var numPoints = points.length;
+            var parts = new Array(numPoints);
+            for (var i = 0; i < numPoints; i++) {
+                parts[i] = points[i].y + " " + points[i].x;
+            }
+            path = parts.join(" ");
+        } else {
+            path = geometry.y + " " + geometry.x;
+        }
+        return this.createTextNode(path);
+    },
+
+    CLASS_NAME: "OpenLayers.Format.GeoRSS" 
+});     
+/* ======================================================================
+    OpenLayers/Format/KML.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Format.KML
+ * Read/Wite KML. Create a new instance with the <OpenLayers.Format.KML>
+ *     constructor. 
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * APIProperty: kmlns
+     * {String} KML Namespace to use. Defaults to 2.0 namespace.
+     */
+    kmlns: "http://earth.google.com/kml/2.0",
+    
+    /** 
+     * APIProperty: placemarksDesc
+     * {String} Name of the placemarks.  Default is "No description available."
+     */
+    placemarksDesc: "No description available",
+    
+    /** 
+     * APIProperty: foldersName
+     * {String} Name of the folders.  Default is "OpenLayers export."
+     */
+    foldersName: "OpenLayers export",
+    
+    /** 
+     * APIProperty: foldersDesc
+     * {String} Description of the folders. Default is "Exported on [date]."
+     */
+    foldersDesc: "Exported on " + new Date(),
+    
+    /**
+     * APIProperty: extractAttributes
+     * {Boolean} Extract attributes from KML.  Default is true.
+     *           Extracting styleUrls requires this to be set to true
+     */
+    extractAttributes: true,
+    
+    /**
+     * Property: extractStyles
+     * {Boolean} Extract styles from KML.  Default is false.
+     *           Extracting styleUrls also requires extractAttributes to be
+     *           set to true
+     */
+    extractStyles: false,
+    
+    /**
+     * Property: internalns
+     * {String} KML Namespace to use -- defaults to the namespace of the
+     *     Placemark node being parsed, but falls back to kmlns. 
+     */
+    internalns: null,
+
+    /**
+     * Property: features
+     * {Array} Array of features
+     *     
+     */
+    features: null,
+
+    /**
+     * Property: styles
+     * {Object} Storage of style objects
+     *     
+     */
+    styles: null,
+    
+    /**
+     * Property: styleBaseUrl
+     * {String}
+     */
+    styleBaseUrl: "",
+
+    /**
+     * Property: fetched
+     * {Object} Storage of KML URLs that have been fetched before
+     *     in order to prevent reloading them.
+     */
+    fetched: null,
+
+    /**
+     * APIProperty: maxDepth
+     * {Integer} Maximum depth for recursive loading external KML URLs 
+     *           Defaults to 0: do no external fetching
+     */
+    maxDepth: 0,
+
+    /**
+     * Constructor: OpenLayers.Format.KML
+     * Create a new parser for KML.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        // compile regular expressions once instead of every time they are used
+        this.regExes = {
+            trimSpace: (/^\s*|\s*$/g),
+            removeSpace: (/\s*/g),
+            splitSpace: (/\s+/),
+            trimComma: (/\s*,\s*/g),
+            kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
+            kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
+            straightBracket: (/\$\[(.*?)\]/g)
+        };
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: read
+     * Read data from a string, and return a list of features. 
+     * 
+     * Parameters: 
+     * data    - {String} or {DOMElement} data to read/parse.
+     *
+     * Returns:
+     * {Array(<OpenLayers.Feature.Vector>)} List of features.
+     */
+    read: function(data) {
+        this.features = [];
+        this.styles   = {};
+        this.fetched  = {};
+
+        // Set default options 
+        var options = {
+            depth: this.maxDepth,
+            styleBaseUrl: this.styleBaseUrl
+        };
+
+        return this.parseData(data, options);
+    },
+
+    /**
+     * Method: parseData
+     * Read data from a string, and return a list of features. 
+     * 
+     * Parameters: 
+     * data    - {String} or {DOMElement} data to read/parse.
+     * options - {Object} Hash of options
+     *
+     * Returns:
+     * {Array(<OpenLayers.Feature.Vector>)} List of features.
+     */
+    parseData: function(data, options) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+
+        // Loop throught the following node types in this order and
+        // process the nodes found 
+        var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"];
+        for(var i=0, len=types.length; i<len; ++i) {
+            var type = types[i];
+
+            var nodes = this.getElementsByTagNameNS(data, "*", type);
+
+            // skip to next type if no nodes are found
+            if(nodes.length == 0) { 
+                continue;
+            }
+
+            switch (type.toLowerCase()) {
+
+                // Fetch external links 
+                case "link":
+                case "networklink":
+                    this.parseLinks(nodes, options);
+                    break;
+
+                // parse style information
+                case "style":
+                    if (this.extractStyles) {
+                        this.parseStyles(nodes, options);
+                    }
+                    break;
+                case "stylemap":
+                    if (this.extractStyles) {
+                        this.parseStyleMaps(nodes, options);
+                    }
+                    break;
+
+                // parse features
+                case "placemark":
+                    this.parseFeatures(nodes, options);
+                    break;
+            }
+        }
+        
+        return this.features;
+    },
+
+    /**
+     * Method: parseLinks
+     * Finds URLs of linked KML documents and fetches them
+     * 
+     * Parameters: 
+     * nodes   - {Array} of {DOMElement} data to read/parse.
+     * options - {Object} Hash of options
+     * 
+     */
+    parseLinks: function(nodes, options) {
+        
+        // Fetch external links <NetworkLink> and <Link>
+        // Don't do anything if we have reached our maximum depth for recursion
+        if (options.depth >= this.maxDepth) {
+            return false;
+        }
+
+        // increase depth
+        var newOptions = OpenLayers.Util.extend({}, options);
+        newOptions.depth++;
+
+        for(var i=0, len=nodes.length; i<len; i++) {
+            var href = this.parseProperty(nodes[i], "*", "href");
+            if(href && !this.fetched[href]) {
+                this.fetched[href] = true; // prevent reloading the same urls
+                var data = this.fetchLink(href);
+                if (data) {
+                    this.parseData(data, newOptions);
+                }
+            } 
+        }
+
+    },
+
+    /**
+     * Method: fetchLink
+     * Fetches a URL and returns the result
+     * 
+     * Parameters: 
+     * href  - {String} url to be fetched
+     * 
+     */
+    fetchLink: function(href) {
+        var request = OpenLayers.Request.GET({url: href, async: false});
+        if (request) {
+            return request.responseText;
+        }
+    },
+
+    /**
+     * Method: parseStyles
+     * Looks for <Style> nodes in the data and parses them
+     * Also parses <StyleMap> nodes, but only uses the 'normal' key
+     * 
+     * Parameters: 
+     * nodes    - {Array} of {DOMElement} data to read/parse.
+     * options  - {Object} Hash of options
+     * 
+     */
+    parseStyles: function(nodes, options) {
+        for(var i=0, len=nodes.length; i<len; i++) {
+            var style = this.parseStyle(nodes[i]);
+            if(style) {
+                styleName = (options.styleBaseUrl || "") + "#" + style.id;
+                
+                this.styles[styleName] = style;
+            }
+        }
+    },
+
+    /**
+     * Method: parseStyle
+     * Parses the children of a <Style> node and builds the style hash
+     * accordingly
+     * 
+     * Parameters: 
+     * node - {DOMElement} <Style> node
+     * 
+     */
+    parseStyle: function(node) {
+        var style = {};
+        
+        var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle"];
+        var type, nodeList, geometry, parser;
+        for(var i=0, len=types.length; i<len; ++i) {
+            type = types[i];
+            styleTypeNode = this.getElementsByTagNameNS(node, 
+                                                   "*", type)[0];
+            if(!styleTypeNode) { 
+                continue;
+            }
+
+            // only deal with first geometry of this type
+            switch (type.toLowerCase()) {
+                case "linestyle":
+                    var color = this.parseProperty(styleTypeNode, "*", "color");
+                    if (color) {
+                        var matches = (color.toString()).match(
+                                                         this.regExes.kmlColor);
+
+                        // transparency
+                        var alpha = matches[1];
+                        style["strokeOpacity"] = parseInt(alpha, 16) / 255;
+
+                        // rgb colors (google uses bgr)
+                        var b = matches[2]; 
+                        var g = matches[3]; 
+                        var r = matches[4]; 
+                        style["strokeColor"] = "#" + r + g + b;
+                    }
+                    
+                    var width = this.parseProperty(styleTypeNode, "*", "width");
+                    if (width) {
+                        style["strokeWidth"] = width;
+                    }
+
+                case "polystyle":
+                    var color = this.parseProperty(styleTypeNode, "*", "color");
+                    if (color) {
+                        var matches = (color.toString()).match(
+                                                         this.regExes.kmlColor);
+
+                        // transparency
+                        var alpha = matches[1];
+                        style["fillOpacity"] = parseInt(alpha, 16) / 255;
+
+                        // rgb colors (google uses bgr)
+                        var b = matches[2]; 
+                        var g = matches[3]; 
+                        var r = matches[4]; 
+                        style["fillColor"] = "#" + r + g + b;
+                    }
+                    
+                    break;
+                case "iconstyle":
+                    // set scale
+                    var scale = parseFloat(this.parseProperty(styleTypeNode, 
+                                                          "*", "scale") || 1);
+  
+                    // set default width and height of icon
+                    var width = 32 * scale;
+                    var height = 32 * scale;
+
+                    var iconNode = this.getElementsByTagNameNS(styleTypeNode, 
+                                               "*", 
+                                               "Icon")[0];
+                    if (iconNode) {
+                        var href = this.parseProperty(iconNode, "*", "href");
+                        if (href) {                                                   
+
+                            var w = this.parseProperty(iconNode, "*", "w");
+                            var h = this.parseProperty(iconNode, "*", "h");
+
+                            // Settings for Google specific icons that are 64x64
+                            // We set the width and height to 64 and halve the
+                            // scale to prevent icons from being too big
+                            var google = "http://maps.google.com/mapfiles/kml";
+                            if (OpenLayers.String.startsWith(
+                                                 href, google) && !w && !h) {
+                                w = 64;
+                                h = 64;
+                                scale = scale / 2;
+                            }
+                                
+                            // if only dimension is defined, make sure the
+                            // other one has the same value
+                            w = w || h;
+                            h = h || w;
+
+                            if (w) {
+                                width = parseInt(w) * scale;
+                            }
+
+                            if (h) {
+                                height = parseInt(h) * scale;
+                            }
+
+                            // support for internal icons 
+                            //    (/root://icons/palette-x.png)
+                            // x and y tell the position on the palette:
+                            // - in pixels
+                            // - starting from the left bottom
+                            // We translate that to a position in the list 
+                            // and request the appropriate icon from the 
+                            // google maps website
+                            var matches = href.match(this.regExes.kmlIconPalette);
+                            if (matches)  {
+                                var palette = matches[1];
+                                var file_extension = matches[2];
+
+                                var x = this.parseProperty(iconNode, "*", "x");
+                                var y = this.parseProperty(iconNode, "*", "y");
+
+                                var posX = x ? x/32 : 0;
+                                var posY = y ? (7 - y/32) : 7;
+
+                                var pos = posY * 8 + posX;
+                                href = "http://maps.google.com/mapfiles/kml/pal" 
+                                     + palette + "/icon" + pos + file_extension;
+                            }
+
+                            style["graphicOpacity"] = 1; // fully opaque
+                            style["externalGraphic"] = href;
+                        }
+
+                    }
+
+
+                    // hotSpots define the offset for an Icon
+                    var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode, 
+                                               "*", 
+                                               "hotSpot")[0];
+                    if (hotSpotNode) {
+                        var x = parseFloat(hotSpotNode.getAttribute("x"));
+                        var y = parseFloat(hotSpotNode.getAttribute("y"));
+
+                        var xUnits = hotSpotNode.getAttribute("xunits");
+                        if (xUnits == "pixels") {
+                            style["graphicXOffset"] = -x * scale;
+                        }
+                        else if (xUnits == "insetPixels") {
+                            style["graphicXOffset"] = -width + (x * scale);
+                        }
+                        else if (xUnits == "fraction") {
+                            style["graphicXOffset"] = -width * x;
+                        }
+
+                        var yUnits = hotSpotNode.getAttribute("yunits");
+                        if (yUnits == "pixels") {
+                            style["graphicYOffset"] = -height + (y * scale) + 1;
+                        }
+                        else if (yUnits == "insetPixels") {
+                            style["graphicYOffset"] = -(y * scale) + 1;
+                        }
+                        else if (yUnits == "fraction") {
+                            style["graphicYOffset"] =  -height * (1 - y) + 1;
+                        }
+                    }
+
+                    style["graphicWidth"] = width;
+                    style["graphicHeight"] = height;
+                    break;
+
+                case "balloonstyle":
+                    var balloonStyle = OpenLayers.Util.getXmlNodeValue(
+                                            styleTypeNode);
+                    if (balloonStyle) {
+                        style["balloonStyle"] = balloonStyle.replace(
+                                       this.regExes.straightBracket, "${$1}");
+                    }
+                    break;
+                default:
+            }
+        }
+
+        // Some polygons have no line color, so we use the fillColor for that
+        if (!style["strokeColor"] && style["fillColor"]) {
+            style["strokeColor"] = style["fillColor"];
+        }
+
+        var id = node.getAttribute("id");
+        if (id && style) {
+            style.id = id;
+        }
+
+        return style;
+    },
+
+    /**
+     * Method: parseStyleMaps
+     * Looks for <Style> nodes in the data and parses them
+     * Also parses <StyleMap> nodes, but only uses the 'normal' key
+     * 
+     * Parameters: 
+     * nodes    - {Array} of {DOMElement} data to read/parse.
+     * options  - {Object} Hash of options
+     * 
+     */
+    parseStyleMaps: function(nodes, options) {
+        // Only the default or "normal" part of the StyleMap is processed now
+        // To do the select or "highlight" bit, we'd need to change lots more
+
+        for(var i=0, len=nodes.length; i<len; i++) {
+            var node = nodes[i];
+            var pairs = this.getElementsByTagNameNS(node, "*", 
+                            "Pair");
+
+            var id = node.getAttribute("id");
+            for (var j=0, jlen=pairs.length; j<jlen; j++) {
+                var pair = pairs[j];
+                // Use the shortcut in the SLD format to quickly retrieve the 
+                // value of a node. Maybe it's good to have a method in 
+                // Format.XML to do this
+                var key = this.parseProperty(pair, "*", "key");
+                var styleUrl = this.parseProperty(pair, "*", "styleUrl");
+
+                if (styleUrl && key == "normal") {
+                    this.styles[(options.styleBaseUrl || "") + "#" + id] =
+                        this.styles[(options.styleBaseUrl || "") + styleUrl];
+                }
+
+                if (styleUrl && key == "highlight") {
+                    // TODO: implement the "select" part
+                }
+
+            }
+        }
+
+    },
+
+
+    /**
+     * Method: parseFeatures
+     * Loop through all Placemark nodes and parse them.
+     * Will create a list of features
+     * 
+     * Parameters: 
+     * nodes    - {Array} of {DOMElement} data to read/parse.
+     * options  - {Object} Hash of options
+     * 
+     */
+    parseFeatures: function(nodes, options) {
+        var features = new Array(nodes.length);
+        for(var i=0, len=nodes.length; i<len; i++) {
+            var featureNode = nodes[i];
+            var feature = this.parseFeature.apply(this,[featureNode]) ;
+            if(feature) {
+
+                // Create reference to styleUrl 
+                if (this.extractStyles && feature.attributes &&
+                    feature.attributes.styleUrl) {
+                    feature.style = this.getStyle(feature.attributes.styleUrl);
+                }
+
+                if (this.extractStyles) {
+                    // Make sure that <Style> nodes within a placemark are 
+                    // processed as well
+                    var inlineStyleNode = this.getElementsByTagNameNS(featureNode,
+                                                        "*",
+                                                        "Style")[0];
+                    if (inlineStyleNode) {
+                        var inlineStyle= this.parseStyle(inlineStyleNode);
+                        if (inlineStyle) {
+                            feature.style = OpenLayers.Util.extend(
+                                feature.style, inlineStyle
+                            );
+                        }
+                    }
+                }
+
+                // add feature to list of features
+                features[i] = feature;
+            } else {
+                throw "Bad Placemark: " + i;
+            }
+        }
+
+        // add new features to existing feature list
+        this.features = this.features.concat(features);
+    },
+
+    /**
+     * Method: parseFeature
+     * This function is the core of the KML parsing code in OpenLayers.
+     *     It creates the geometries that are then attached to the returned
+     *     feature, and calls parseAttributes() to get attribute data out.
+     *
+     * Parameters:
+     * node - {DOMElement}
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A vector feature.
+     */
+    parseFeature: function(node) {
+        // only accept one geometry per feature - look for highest "order"
+        var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
+        var type, nodeList, geometry, parser;
+        for(var i=0, len=order.length; i<len; ++i) {
+            type = order[i];
+            this.internalns = node.namespaceURI ? 
+                    node.namespaceURI : this.kmlns;
+            nodeList = this.getElementsByTagNameNS(node, 
+                                                   this.internalns, type);
+            if(nodeList.length > 0) {
+                // only deal with first geometry of this type
+                var parser = this.parseGeometry[type.toLowerCase()];
+                if(parser) {
+                    geometry = parser.apply(this, [nodeList[0]]);
+                    if (this.internalProjection && this.externalProjection) {
+                        geometry.transform(this.externalProjection, 
+                                           this.internalProjection); 
+                    }                       
+                } else {
+                    OpenLayers.Console.error(OpenLayers.i18n(
+                                "unsupportedGeometryType", {'geomType':type}));
+                }
+                // stop looking for different geometry types
+                break;
+            }
+        }
+
+        // construct feature (optionally with attributes)
+        var attributes;
+        if(this.extractAttributes) {
+            attributes = this.parseAttributes(node);
+        }
+        var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+
+        var fid = node.getAttribute("id") || node.getAttribute("name");
+        if(fid != null) {
+            feature.fid = fid;
+        }
+
+        return feature;
+    },        
+    
+    /**
+     * Method: getStyle
+     * Retrieves a style from a style hash using styleUrl as the key
+     * If the styleUrl doesn't exist yet, we try to fetch it 
+     * Internet
+     * 
+     * Parameters: 
+     * styleUrl  - {String} URL of style
+     * options   - {Object} Hash of options 
+     *
+     * Returns:
+     * {Object}  - (reference to) Style hash
+     */
+    getStyle: function(styleUrl, options) {
+
+        var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl);
+
+        var newOptions = OpenLayers.Util.extend({}, options);
+        newOptions.depth++;
+        newOptions.styleBaseUrl = styleBaseUrl;
+
+        // Fetch remote Style URLs (if not fetched before) 
+        if (!this.styles[styleUrl] 
+                && !OpenLayers.String.startsWith(styleUrl, "#") 
+                && newOptions.depth <= this.maxDepth
+                && !this.fetched[styleBaseUrl] ) {
+
+            var data = this.fetchLink(styleBaseUrl);
+            if (data) {
+                this.parseData(data, newOptions);
+            }
+
+        }
+
+        // return requested style
+        var style = this.styles[styleUrl];
+        return style;
+    },
+    
+    /**
+     * Property: parseGeometry
+     * Properties of this object are the functions that parse geometries based
+     *     on their type.
+     */
+    parseGeometry: {
+        
+        /**
+         * Method: parseGeometry.point
+         * Given a KML node representing a point geometry, create an OpenLayers
+         *     point geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML Point node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Point>} A point geometry.
+         */
+        point: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+                                                       "coordinates");
+            var coords = [];
+            if(nodeList.length > 0) {
+                var coordString = nodeList[0].firstChild.nodeValue;
+                coordString = coordString.replace(this.regExes.removeSpace, "");
+                coords = coordString.split(",");
+            }
+
+            var point = null;
+            if(coords.length > 1) {
+                // preserve third dimension
+                if(coords.length == 2) {
+                    coords[2] = null;
+                }
+                point = new OpenLayers.Geometry.Point(coords[0], coords[1],
+                                                      coords[2]);
+            } else {
+                throw "Bad coordinate string: " + coordString;
+            }
+            return point;
+        },
+        
+        /**
+         * Method: parseGeometry.linestring
+         * Given a KML node representing a linestring geometry, create an
+         *     OpenLayers linestring geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML LineString node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+         */
+        linestring: function(node, ring) {
+            var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+                                                       "coordinates");
+            var line = null;
+            if(nodeList.length > 0) {
+                var coordString = this.concatChildValues(nodeList[0]);
+
+                coordString = coordString.replace(this.regExes.trimSpace,
+                                                  "");
+                coordString = coordString.replace(this.regExes.trimComma,
+                                                  ",");
+                var pointList = coordString.split(this.regExes.splitSpace);
+                var numPoints = pointList.length;
+                var points = new Array(numPoints);
+                var coords, numCoords;
+                for(var i=0; i<numPoints; ++i) {
+                    coords = pointList[i].split(",");
+                    numCoords = coords.length;
+                    if(numCoords > 1) {
+                        if(coords.length == 2) {
+                            coords[2] = null;
+                        }
+                        points[i] = new OpenLayers.Geometry.Point(coords[0],
+                                                                  coords[1],
+                                                                  coords[2]);
+                    } else {
+                        throw "Bad LineString point coordinates: " +
+                              pointList[i];
+                    }
+                }
+                if(numPoints) {
+                    if(ring) {
+                        line = new OpenLayers.Geometry.LinearRing(points);
+                    } else {
+                        line = new OpenLayers.Geometry.LineString(points);
+                    }
+                } else {
+                    throw "Bad LineString coordinates: " + coordString;
+                }
+            }
+
+            return line;
+        },
+        
+        /**
+         * Method: parseGeometry.polygon
+         * Given a KML node representing a polygon geometry, create an
+         *     OpenLayers polygon geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML Polygon node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+         */
+        polygon: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+                                                       "LinearRing");
+            var numRings = nodeList.length;
+            var components = new Array(numRings);
+            if(numRings > 0) {
+                // this assumes exterior ring first, inner rings after
+                var ring;
+                for(var i=0, len=nodeList.length; i<len; ++i) {
+                    ring = this.parseGeometry.linestring.apply(this,
+                                                        [nodeList[i], true]);
+                    if(ring) {
+                        components[i] = ring;
+                    } else {
+                        throw "Bad LinearRing geometry: " + i;
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.Polygon(components);
+        },
+        
+        /**
+         * Method: parseGeometry.multigeometry
+         * Given a KML node representing a multigeometry, create an
+         *     OpenLayers geometry collection.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML MultiGeometry node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Collection>} A geometry collection.
+         */
+        multigeometry: function(node) {
+            var child, parser;
+            var parts = [];
+            var children = node.childNodes;
+            for(var i=0, len=children.length; i<len; ++i ) {
+                child = children[i];
+                if(child.nodeType == 1) {
+                    var type = (child.prefix) ?
+                            child.nodeName.split(":")[1] :
+                            child.nodeName;
+                    var parser = this.parseGeometry[type.toLowerCase()];
+                    if(parser) {
+                        parts.push(parser.apply(this, [child]));
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.Collection(parts);
+        }
+        
+    },
+
+    /**
+     * Method: parseAttributes
+     *
+     * Parameters:
+     * node - {DOMElement}
+     *
+     * Returns:
+     * {Object} An attributes object.
+     */
+    parseAttributes: function(node) {
+        var attributes = {};
+        // assume attribute nodes are type 1 children with a type 3 or 4 child
+        var child, grandchildren, grandchild;
+        var children = node.childNodes;
+        for(var i=0, len=children.length; i<len; ++i) {
+            child = children[i];
+            if(child.nodeType == 1) {
+                grandchildren = child.childNodes;
+                if(grandchildren.length == 1 || grandchildren.length == 3) {
+                    var grandchild;
+                    switch (grandchildren.length) {
+                        case 1:
+                            grandchild = grandchildren[0];
+                            break;
+                        case 3:
+                        default:
+                            grandchild = grandchildren[1];
+                            break;
+                    }
+                    if(grandchild.nodeType == 3 || grandchild.nodeType == 4) {
+                        var name = (child.prefix) ?
+                                child.nodeName.split(":")[1] :
+                                child.nodeName;
+                        var value = OpenLayers.Util.getXmlNodeValue(grandchild);
+                        if (value) {
+                            value = value.replace(this.regExes.trimSpace, "");
+                            attributes[name] = value;
+                        }
+                    }
+                } 
+            }
+        }
+        return attributes;
+    },
+
+    
+    /**
+     * Method: parseProperty
+     * Convenience method to find a node and return its value
+     *
+     * Parameters:
+     * xmlNode    - {<DOMElement>}
+     * namespace  - {String} namespace of the node to find
+     * tagName    - {String} name of the property to parse
+     * 
+     * Returns:
+     * {String} The value for the requested property (defaults to null)
+     */    
+    parseProperty: function(xmlNode, namespace, tagName) {
+        var value;
+        var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName);
+        try {
+            value = OpenLayers.Util.getXmlNodeValue(nodeList[0]);
+        } catch(e) {
+            value = null;
+        }
+     
+        return value;
+    },                                                              
+
+    /**
+     * APIMethod: write
+     * Accept Feature Collection, and return a string. 
+     * 
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>} An array of features.
+     *
+     * Returns:
+     * {String} A KML string.
+     */
+    write: function(features) {
+        if(!(features instanceof Array)) {
+            features = [features];
+        }
+        var kml = this.createElementNS(this.kmlns, "kml");
+        var folder = this.createFolderXML();
+        for(var i=0, len=features.length; i<len; ++i) {
+            folder.appendChild(this.createPlacemarkXML(features[i]));
+        }
+        kml.appendChild(folder);
+        return OpenLayers.Format.XML.prototype.write.apply(this, [kml]);
+    },
+
+    /**
+     * Method: createFolderXML
+     * Creates and returns a KML folder node
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    createFolderXML: function() {
+        // Folder name
+        var folderName = this.createElementNS(this.kmlns, "name");
+        var folderNameText = this.createTextNode(this.foldersName); 
+        folderName.appendChild(folderNameText);
+
+        // Folder description
+        var folderDesc = this.createElementNS(this.kmlns, "description");        
+        var folderDescText = this.createTextNode(this.foldersDesc); 
+        folderDesc.appendChild(folderDescText);
+
+        // Folder
+        var folder = this.createElementNS(this.kmlns, "Folder");
+        folder.appendChild(folderName);
+        folder.appendChild(folderDesc);
+        
+        return folder;
+    },
+
+    /**
+     * Method: createPlacemarkXML
+     * Creates and returns a KML placemark node representing the given feature. 
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    createPlacemarkXML: function(feature) {        
+        // Placemark name
+        var placemarkName = this.createElementNS(this.kmlns, "name");
+        var name = (feature.attributes.name) ?
+                    feature.attributes.name : feature.id;
+        placemarkName.appendChild(this.createTextNode(name));
+
+        // Placemark description
+        var placemarkDesc = this.createElementNS(this.kmlns, "description");
+        var desc = (feature.attributes.description) ?
+                    feature.attributes.description : this.placemarksDesc;
+        placemarkDesc.appendChild(this.createTextNode(desc));
+        
+        // Placemark
+        var placemarkNode = this.createElementNS(this.kmlns, "Placemark");
+        if(feature.fid != null) {
+            placemarkNode.setAttribute("id", feature.fid);
+        }
+        placemarkNode.appendChild(placemarkName);
+        placemarkNode.appendChild(placemarkDesc);
+
+        // Geometry node (Point, LineString, etc. nodes)
+        var geometryNode = this.buildGeometryNode(feature.geometry);
+        placemarkNode.appendChild(geometryNode);        
+        
+        // TBD - deal with remaining (non name/description) attributes.
+        return placemarkNode;
+    },    
+
+    /**
+     * Method: buildGeometryNode
+     * Builds and returns a KML geometry node with the given geometry.
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    buildGeometryNode: function(geometry) {
+        if (this.internalProjection && this.externalProjection) {
+            geometry = geometry.clone();
+            geometry.transform(this.internalProjection, 
+                               this.externalProjection);
+        }                       
+        var className = geometry.CLASS_NAME;
+        var type = className.substring(className.lastIndexOf(".") + 1);
+        var builder = this.buildGeometry[type.toLowerCase()];
+        var node = null;
+        if(builder) {
+            node = builder.apply(this, [geometry]);
+        }
+        return node;
+    },
+
+    /**
+     * Property: buildGeometry
+     * Object containing methods to do the actual geometry node building
+     *     based on geometry type.
+     */
+    buildGeometry: {
+        // TBD: Anybody care about namespace aliases here (these nodes have
+        //    no prefixes)?
+
+        /**
+         * Method: buildGeometry.point
+         * Given an OpenLayers point geometry, create a KML point.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML point node.
+         */
+        point: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "Point");
+            kml.appendChild(this.buildCoordinatesNode(geometry));
+            return kml;
+        },
+        
+        /**
+         * Method: buildGeometry.multipoint
+         * Given an OpenLayers multipoint geometry, create a KML
+         *     GeometryCollection.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A multipoint geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML GeometryCollection node.
+         */
+        multipoint: function(geometry) {
+            return this.buildGeometry.collection.apply(this, [geometry]);
+        },
+
+        /**
+         * Method: buildGeometry.linestring
+         * Given an OpenLayers linestring geometry, create a KML linestring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML linestring node.
+         */
+        linestring: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "LineString");
+            kml.appendChild(this.buildCoordinatesNode(geometry));
+            return kml;
+        },
+        
+        /**
+         * Method: buildGeometry.multilinestring
+         * Given an OpenLayers multilinestring geometry, create a KML
+         *     GeometryCollection.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A multilinestring geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML GeometryCollection node.
+         */
+        multilinestring: function(geometry) {
+            return this.buildGeometry.collection.apply(this, [geometry]);
+        },
+
+        /**
+         * Method: buildGeometry.linearring
+         * Given an OpenLayers linearring geometry, create a KML linearring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML linearring node.
+         */
+        linearring: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "LinearRing");
+            kml.appendChild(this.buildCoordinatesNode(geometry));
+            return kml;
+        },
+        
+        /**
+         * Method: buildGeometry.polygon
+         * Given an OpenLayers polygon geometry, create a KML polygon.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML polygon node.
+         */
+        polygon: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "Polygon");
+            var rings = geometry.components;
+            var ringMember, ringGeom, type;
+            for(var i=0, len=rings.length; i<len; ++i) {
+                type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+                ringMember = this.createElementNS(this.kmlns, type);
+                ringGeom = this.buildGeometry.linearring.apply(this,
+                                                               [rings[i]]);
+                ringMember.appendChild(ringGeom);
+                kml.appendChild(ringMember);
+            }
+            return kml;
+        },
+        
+        /**
+         * Method: buildGeometry.multipolygon
+         * Given an OpenLayers multipolygon geometry, create a KML
+         *     GeometryCollection.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A multipolygon geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML GeometryCollection node.
+         */
+        multipolygon: function(geometry) {
+            return this.buildGeometry.collection.apply(this, [geometry]);
+        },
+
+        /**
+         * Method: buildGeometry.collection
+         * Given an OpenLayers geometry collection, create a KML MultiGeometry.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Collection>} A geometry collection.
+         *
+         * Returns:
+         * {DOMElement} A KML MultiGeometry node.
+         */
+        collection: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "MultiGeometry");
+            var child;
+            for(var i=0, len=geometry.components.length; i<len; ++i) {
+                child = this.buildGeometryNode.apply(this,
+                                                     [geometry.components[i]]);
+                if(child) {
+                    kml.appendChild(child);
+                }
+            }
+            return kml;
+        }
+    },
+
+    /**
+     * Method: buildCoordinatesNode
+     * Builds and returns the KML coordinates node with the given geometry
+     * <coordinates>...</coordinates>
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Return:
+     * {DOMElement}
+     */     
+    buildCoordinatesNode: function(geometry) {
+        var coordinatesNode = this.createElementNS(this.kmlns, "coordinates");
+        
+        var path;
+        var points = geometry.components;
+        if(points) {
+            // LineString or LinearRing
+            var point;
+            var numPoints = points.length;
+            var parts = new Array(numPoints);
+            for(var i=0; i<numPoints; ++i) {
+                point = points[i];
+                parts[i] = point.x + "," + point.y;
+            }
+            path = parts.join(" ");
+        } else {
+            // Point
+            path = geometry.x + "," + geometry.y;
+        }
+        
+        var txtNode = this.createTextNode(path);
+        coordinatesNode.appendChild(txtNode);
+        
+        return coordinatesNode;
+    },    
+
+    CLASS_NAME: "OpenLayers.Format.KML" 
+});
+/* ======================================================================
+    OpenLayers/Format/OSM.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**  
+ * Class: OpenLayers.Format.OSM
+ * OSM parser. Create a new instance with the 
+ *     <OpenLayers.Format.OSM> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OSM = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * APIProperty: checkTags
+     * {Boolean} Should tags be checked to determine whether something
+     * should be treated as a seperate node. Will slow down parsing.
+     * Default is false.
+     */
+    checkTags: false,
+
+    /**
+     * Property: interestingTagsExclude
+     * {Array} List of tags to exclude from 'interesting' checks on nodes.
+     * Must be set when creating the format. Will only be used if checkTags
+     * is set.
+     */
+    interestingTagsExclude: null, 
+    
+    /**
+     * APIProperty: areaTags
+     * {Array} List of tags indicating that something is an area.  
+     * Must be set when creating the format. Will only be used if 
+     * checkTags is true.
+     */
+    areaTags: null, 
+    
+    /**
+     * Constructor: OpenLayers.Format.OSM
+     * Create a new parser for OSM.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        var layer_defaults = {
+          'interestingTagsExclude': ['source', 'source_ref', 
+              'source:ref', 'history', 'attribution', 'created_by'],
+          'areaTags': ['area', 'building', 'leisure', 'tourism', 'ruins',
+              'historic', 'landuse', 'military', 'natural', 'sport'] 
+        };
+          
+        layer_defaults = OpenLayers.Util.extend(layer_defaults, options);
+        
+        var interesting = {};
+        for (var i = 0; i < layer_defaults.interestingTagsExclude.length; i++) {
+            interesting[layer_defaults.interestingTagsExclude[i]] = true;
+        }
+        layer_defaults.interestingTagsExclude = interesting;
+        
+        var area = {};
+        for (var i = 0; i < layer_defaults.areaTags.length; i++) {
+            area[layer_defaults.areaTags[i]] = true;
+        }
+        layer_defaults.areaTags = area;
+        
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [layer_defaults]);
+    },
+    
+    /**
+     * APIMethod: read
+     * Return a list of features from a OSM doc
+     
+     * Parameters:
+     * data - {Element} 
+     *
+     * Returns:
+     * An Array of <OpenLayers.Feature.Vector>s
+     */
+    read: function(doc) {
+        if (typeof doc == "string") { 
+            doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+        }
+
+        var nodes = this.getNodes(doc);
+        var ways = this.getWays(doc);
+        
+        // Geoms will contain at least ways.length entries.
+        var feat_list = new Array(ways.length);
+        
+        for (var i = 0; i < ways.length; i++) {
+            // We know the minimal of this one ahead of time. (Could be -1
+            // due to areas/polygons)
+            var point_list = new Array(ways[i].nodes.length);
+            
+            var poly = this.isWayArea(ways[i]) ? 1 : 0; 
+            for (var j = 0; j < ways[i].nodes.length; j++) {
+               var node = nodes[ways[i].nodes[j]];
+               
+               var point = new OpenLayers.Geometry.Point(node.lon, node.lat);
+               
+               // Since OSM is topological, we stash the node ID internally. 
+               point.osm_id = parseInt(ways[i].nodes[j]);
+               point_list[j] = point;
+               
+               // We don't display nodes if they're used inside other 
+               // elements.
+               node.used = true; 
+            }
+            var geometry = null;
+            if (poly) { 
+                geometry = new OpenLayers.Geometry.Polygon(
+                    new OpenLayers.Geometry.LinearRing(point_list));
+            } else {    
+                geometry = new OpenLayers.Geometry.LineString(point_list);
+            }
+            if (this.internalProjection && this.externalProjection) {
+                geometry.transform(this.externalProjection, 
+                    this.internalProjection);
+            }        
+            var feat = new OpenLayers.Feature.Vector(geometry,
+                ways[i].tags);
+            feat.osm_id = parseInt(ways[i].id);
+            feat.fid = "way." + feat.osm_id;
+            feat_list[i] = feat;
+        } 
+        for (var node_id in nodes) {
+            var node = nodes[node_id];
+            if (!node.used || this.checkTags) {
+                var tags = null;
+                
+                if (this.checkTags) {
+                    var result = this.getTags(node.node, true);
+                    if (node.used && !result[1]) {
+                        continue;
+                    }
+                    tags = result[0];
+                } else { 
+                    tags = this.getTags(node.node);
+                }    
+                
+                var feat = new OpenLayers.Feature.Vector(
+                    new OpenLayers.Geometry.Point(node['lon'], node['lat']),
+                    tags);
+                if (this.internalProjection && this.externalProjection) {
+                    feat.geometry.transform(this.externalProjection, 
+                        this.internalProjection);
+                }        
+                feat.osm_id = parseInt(node_id); 
+                feat.fid = "node." + feat.osm_id;
+                feat_list.push(feat);
+            }   
+            // Memory cleanup
+            node.node = null;
+        }        
+        return feat_list;
+    },
+
+    /**
+     * Method: getNodes
+     * Return the node items from a doc.  
+     *
+     * Parameters:
+     * node - {DOMElement} node to parse tags from
+     */
+    getNodes: function(doc) {
+        var node_list = doc.getElementsByTagName("node");
+        var nodes = {};
+        for (var i = 0; i < node_list.length; i++) {
+            var node = node_list[i];
+            var id = node.getAttribute("id");
+            nodes[id] = {
+                'lat': node.getAttribute("lat"),
+                'lon': node.getAttribute("lon"),
+                'node': node
+            };
+        }
+        return nodes;
+    },
+
+    /**
+     * Method: getWays
+     * Return the way items from a doc.  
+     *
+     * Parameters:
+     * node - {DOMElement} node to parse tags from
+     */
+    getWays: function(doc) {
+        var way_list = doc.getElementsByTagName("way");
+        var return_ways = [];
+        for (var i = 0; i < way_list.length; i++) {
+            var way = way_list[i];
+            var way_object = {
+              id: way.getAttribute("id")
+            };
+            
+            way_object.tags = this.getTags(way);
+            
+            var node_list = way.getElementsByTagName("nd");
+            
+            way_object.nodes = new Array(node_list.length);
+            
+            for (var j = 0; j < node_list.length; j++) {
+                way_object.nodes[j] = node_list[j].getAttribute("ref");
+            }  
+            return_ways.push(way_object);
+        }
+        return return_ways; 
+        
+    },  
+    
+    /**
+     * Method: getTags
+     * Return the tags list attached to a specific DOM element.
+     *
+     * Parameters:
+     * node - {DOMElement} node to parse tags from
+     * interesting_tags - {Boolean} whether the return from this function should
+     *    return a boolean indicating that it has 'interesting tags' -- 
+     *    tags like attribution and source are ignored. (To change the list
+     *    of tags, see interestingTagsExclude)
+     * 
+     * Returns:
+     * tags - {Object} hash of tags
+     * interesting - {Boolean} if interesting_tags is passed, returns
+     *     whether there are any interesting tags on this element.
+     */
+    getTags: function(dom_node, interesting_tags) {
+        var tag_list = dom_node.getElementsByTagName("tag");
+        var tags = {};
+        var interesting = false;
+        for (var j = 0; j < tag_list.length; j++) {
+            var key = tag_list[j].getAttribute("k");
+            tags[key] = tag_list[j].getAttribute("v");
+            if (interesting_tags) {
+                if (!this.interestingTagsExclude[key]) {
+                    interesting = true;
+                }
+            }    
+        }  
+        return interesting_tags ? [tags, interesting] : tags;     
+    },
+
+    /** 
+     * Method: isWayArea
+     * Given a way object from getWays, check whether the tags and geometry
+     * indicate something is an area.
+     *
+     * Returns:
+     * {Boolean}
+     */
+    isWayArea: function(way) { 
+        var poly_shaped = false;
+        var poly_tags = false;
+        
+        if (way.nodes[0] == way.nodes[way.nodes.length - 1]) {
+            poly_shaped = true;
+        }
+        if (this.checkTags) {
+            for(var key in way.tags) {
+                if (this.areaTags[key]) {
+                    poly_tags = true;
+                    break;
+                }
+            }
+        }    
+        return poly_shaped && (this.checkTags ? poly_tags : true);            
+    }, 
+
+    /**
+     * APIMethod: write 
+     * Takes a list of features, returns a serialized OSM format file for use
+     * in tools like JOSM.
+     *
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)}
+     */
+    write: function(features) { 
+        if (!(features instanceof Array)) {
+            features = [features];
+        }
+        
+        this.osm_id = 1;
+        this.created_nodes = {};
+        var root_node = this.createElementNS(null, "osm");
+        root_node.setAttribute("version", "0.5");
+        root_node.setAttribute("generator", "OpenLayers "+ OpenLayers.VERSION_NUMBER);
+
+        // Loop backwards, because the deserializer puts nodes last, and 
+        // we want them first if possible
+        for(var i = features.length - 1; i >= 0; i--) {
+            var nodes = this.createFeatureNodes(features[i]);
+            for (var j = 0; j < nodes.length; j++) {
+                root_node.appendChild(nodes[j]);
+            }    
+        }
+        return OpenLayers.Format.XML.prototype.write.apply(this, [root_node]);
+    },
+
+    /**
+     * Method: createFeatureNodes
+     * Takes a feature, returns a list of nodes from size 0->n.
+     * Will include all pieces of the serialization that are required which
+     * have not already been created. Calls out to createXML based on geometry
+     * type.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     */
+    createFeatureNodes: function(feature) {
+        var nodes = [];
+        var className = feature.geometry.CLASS_NAME;
+        var type = className.substring(className.lastIndexOf(".") + 1);
+        type = type.toLowerCase();
+        var builder = this.createXML[type];
+        if (builder) {
+            nodes = builder.apply(this, [feature]);
+        }
+        return nodes;
+    },
+    
+    /**
+     * Method: createXML
+     * Takes a feature, returns a list of nodes from size 0->n.
+     * Will include all pieces of the serialization that are required which
+     * have not already been created.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     */
+    createXML: {
+        'point': function(point) {
+            var id = null;
+            var geometry = point.geometry ? point.geometry : point;
+            var already_exists = false; // We don't return anything if the node
+                                        // has already been created
+            if (point.osm_id) {
+                id = point.osm_id;
+                if (this.created_nodes[id]) {
+                    already_exists = true;
+                }    
+            } else {
+               id = -this.osm_id;
+               this.osm_id++; 
+            }
+            if (already_exists) {
+                node = this.created_nodes[id];
+            } else {    
+                var node = this.createElementNS(null, "node");
+            }
+            this.created_nodes[id] = node;
+            node.setAttribute("id", id);
+            node.setAttribute("lon", geometry.x); 
+            node.setAttribute("lat", geometry.y);
+            if (point.attributes) {
+                this.serializeTags(point, node);
+            }
+            this.setState(point, node);
+            return already_exists ? [] : [node];
+        }, 
+        linestring: function(feature) {
+            var nodes = [];
+            var geometry = feature.geometry;
+            if (feature.osm_id) {
+                id = feature.osm_id;
+            } else {
+               id = -this.osm_id;
+               this.osm_id++; 
+            }
+            var way = this.createElementNS(null, "way");
+            way.setAttribute("id", id);
+            for (var i = 0; i < geometry.components.length; i++) {
+                var node = this.createXML['point'].apply(this, [geometry.components[i]]);
+                if (node.length) {
+                    node = node[0];
+                    var node_ref = node.getAttribute("id");
+                    nodes.push(node);
+                } else {
+                    node_ref = geometry.components[i].osm_id;
+                    node = this.created_nodes[node_ref];
+                }
+                this.setState(feature, node);
+                var nd_dom = this.createElementNS(null, "nd");
+                nd_dom.setAttribute("ref", node_ref);
+                way.appendChild(nd_dom);
+            }
+            this.serializeTags(feature, way);
+            nodes.push(way);
+            
+            return nodes;
+        },
+        polygon: function(feature) {
+            var attrs = OpenLayers.Util.extend({'area':'yes'}, feature.attributes);
+            var feat = new OpenLayers.Feature.Vector(feature.geometry.components[0], attrs); 
+            feat.osm_id = feature.osm_id;
+            return this.createXML['linestring'].apply(this, [feat]);
+        }
+    },
+
+    /**
+     * Method: serializeTags
+     * Given a feature, serialize the attributes onto the given node.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     * node - {DOMNode}
+     */
+    serializeTags: function(feature, node) {
+        for (var key in feature.attributes) {
+            var tag = this.createElementNS(null, "tag");
+            tag.setAttribute("k", key);
+            tag.setAttribute("v", feature.attributes[key]);
+            node.appendChild(tag);
+        }
+    },
+
+    /**
+     * Method: setState 
+     * OpenStreetMap has a convention that 'state' is stored for modification or deletion.
+     * This allows the file to be uploaded via JOSM or the bulk uploader tool.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     * node - {DOMNode}
+     */
+    setState: function(feature, node) {
+        if (feature.state) {
+            var state = null;
+            switch(feature.state) {
+                case OpenLayers.State.UPDATE:
+                    state = "modify";
+                case OpenLayers.State.DELETE:
+                    state = "delete";
+            }
+            if (state) {
+                node.setAttribute("action", state);
+            }
+        }    
+    },
+
+    CLASS_NAME: "OpenLayers.Format.OSM" 
+});     
+/* ======================================================================
+    OpenLayers/Geometry/LinearRing.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LinearRing
+ * 
+ * A Linear Ring is a special LineString which is closed. It closes itself 
+ * automatically on every addPoint/removePoint by adding a copy of the first
+ * point as the last point. 
+ * 
+ * Also, as it is the first in the line family to close itself, a getArea()
+ * function is defined to calculate the enclosed area of the linearRing
+ * 
+ * Inherits:
+ *  - <OpenLayers.Geometry.LineString>
+ */
+OpenLayers.Geometry.LinearRing = OpenLayers.Class(
+  OpenLayers.Geometry.LineString, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of 
+     *                 components that the collection can include.  A null 
+     *                 value means the component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.Point"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.LinearRing
+     * Linear rings are constructed with an array of points.  This array
+     *     can represent a closed or open ring.  If the ring is open (the last
+     *     point does not equal the first point), the constructor will close
+     *     the ring.  If the ring is already closed (the last point does equal
+     *     the first point), it will be left closed.
+     * 
+     * Parameters:
+     * points - {Array(<OpenLayers.Geometry.Point>)} points
+     */
+    initialize: function(points) {
+        OpenLayers.Geometry.LineString.prototype.initialize.apply(this, 
+                                                                  arguments);
+    },
+
+    /**
+     * APIMethod: addComponent
+     * Adds a point to geometry components.  If the point is to be added to
+     *     the end of the components array and it is the same as the last point
+     *     already in that array, the duplicate point is not added.  This has 
+     *     the effect of closing the ring if it is not already closed, and 
+     *     doing the right thing if it is already closed.  This behavior can 
+     *     be overridden by calling the method with a non-null index as the 
+     *     second argument.
+     *
+     * Parameter:
+     * point - {<OpenLayers.Geometry.Point>}
+     * index - {Integer} Index into the array to insert the component
+     * 
+     * Returns:
+     * {Boolean} Was the Point successfully added?
+     */
+    addComponent: function(point, index) {
+        var added = false;
+
+        //remove last point
+        var lastPoint = this.components.pop();
+
+        // given an index, add the point
+        // without an index only add non-duplicate points
+        if(index != null || !point.equals(lastPoint)) {
+            added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
+                                                                    arguments);
+        }
+
+        //append copy of first point
+        var firstPoint = this.components[0];
+        OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
+                                                                [firstPoint]);
+        
+        return added;
+    },
+    
+    /**
+     * APIMethod: removeComponent
+     * Removes a point from geometry components.
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>}
+     */
+    removeComponent: function(point) {
+        if (this.components.length > 4) {
+
+            //remove last point
+            this.components.pop();
+            
+            //remove our point
+            OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, 
+                                                                    arguments);
+            //append copy of first point
+            var firstPoint = this.components[0];
+            OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
+                                                                [firstPoint]);
+        }
+    },
+    
+    /**
+     * APIMethod: move
+     * Moves a collection in place
+     *
+     * Parameters:
+     * x - {Float} The x-displacement (in map units)
+     * y - {Float} The y-displacement (in map units)
+     */
+    move: function(x, y) {
+        for(var i = 0, len=this.components.length; i<len - 1; i++) {
+            this.components[i].move(x, y);
+        }
+    },
+
+    /**
+     * APIMethod: rotate
+     * Rotate a geometry around some origin
+     *
+     * Parameters:
+     * angle - {Float} Rotation angle in degrees (measured counterclockwise
+     *                 from the positive x-axis)
+     * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+     */
+    rotate: function(angle, origin) {
+        for(var i=0, len=this.components.length; i<len - 1; ++i) {
+            this.components[i].rotate(angle, origin);
+        }
+    },
+
+    /**
+     * APIMethod: resize
+     * Resize a geometry relative to some origin.  Use this method to apply
+     *     a uniform scaling to a geometry.
+     *
+     * Parameters:
+     * scale - {Float} Factor by which to scale the geometry.  A scale of 2
+     *                 doubles the size of the geometry in each dimension
+     *                 (lines, for example, will be twice as long, and polygons
+     *                 will have four times the area).
+     * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+     * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
+     */
+    resize: function(scale, origin, ratio) {
+        for(var i=0, len=this.components.length; i<len - 1; ++i) {
+            this.components[i].resize(scale, origin, ratio);
+        }
+    },
+    
+    /**
+     * APIMethod: transform
+     * Reproject the components geometry from source to dest.
+     *
+     * Parameters:
+     * source - {<OpenLayers.Projection>}
+     * dest - {<OpenLayers.Projection>}
+     * 
+     * Returns:
+     * {<OpenLayers.Geometry>} 
+     */
+    transform: function(source, dest) {
+        if (source && dest) {
+            for (var i=0, len=this.components.length; i<len - 1; i++) {
+                var component = this.components[i];
+                component.transform(source, dest);
+            }
+            this.bounds = null;
+        }
+        return this;
+    },
+
+    /**
+     * APIMethod: getArea
+     * Note - The area is positive if the ring is oriented CW, otherwise
+     *         it will be negative.
+     * 
+     * Returns:
+     * {Float} The signed area for a ring.
+     */
+    getArea: function() {
+        var area = 0.0;
+        if ( this.components && (this.components.length > 2)) {
+            var sum = 0.0;
+            for (var i=0, len=this.components.length; i<len - 1; i++) {
+                var b = this.components[i];
+                var c = this.components[i+1];
+                sum += (b.x + c.x) * (c.y - b.y);
+            }
+            area = - sum / 2.0;
+        }
+        return area;
+    },
+    
+    /**
+     * Method: containsPoint
+     * Test if a point is inside a linear ring.  For the case where a point
+     *     is coincident with a linear ring edge, returns 1.  Otherwise,
+     *     returns boolean.
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>}
+     *
+     * Returns:
+     * {Boolean | Number} The point is inside the linear ring.  Returns 1 if
+     *     the point is coincident with an edge.  Returns boolean otherwise.
+     */
+    containsPoint: function(point) {
+        var approx = OpenLayers.Number.limitSigDigs;
+        var digs = 14;
+        var px = approx(point.x, digs);
+        var py = approx(point.y, digs);
+        function getX(y, x1, y1, x2, y2) {
+            return (((x1 - x2) * y) + ((x2 * y1) - (x1 * y2))) / (y1 - y2);
+        }
+        var numSeg = this.components.length - 1;
+        var start, end, x1, y1, x2, y2, cx, cy;
+        var crosses = 0;
+        for(var i=0; i<numSeg; ++i) {
+            start = this.components[i];
+            x1 = approx(start.x, digs);
+            y1 = approx(start.y, digs);
+            end = this.components[i + 1];
+            x2 = approx(end.x, digs);
+            y2 = approx(end.y, digs);
+            
+            /**
+             * The following conditions enforce five edge-crossing rules:
+             *    1. points coincident with edges are considered contained;
+             *    2. an upward edge includes its starting endpoint, and
+             *    excludes its final endpoint;
+             *    3. a downward edge excludes its starting endpoint, and
+             *    includes its final endpoint;
+             *    4. horizontal edges are excluded; and
+             *    5. the edge-ray intersection point must be strictly right
+             *    of the point P.
+             */
+            if(y1 == y2) {
+                // horizontal edge
+                if(py == y1) {
+                    // point on horizontal line
+                    if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
+                       x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
+                        // point on edge
+                        crosses = -1;
+                        break;
+                    }
+                }
+                // ignore other horizontal edges
+                continue;
+            }
+            cx = approx(getX(py, x1, y1, x2, y2), digs);
+            if(cx == px) {
+                // point on line
+                if(y1 < y2 && (py >= y1 && py <= y2) || // upward
+                   y1 > y2 && (py <= y1 && py >= y2)) { // downward
+                    // point on edge
+                    crosses = -1;
+                    break;
+                }
+            }
+            if(cx <= px) {
+                // no crossing to the right
+                continue;
+            }
+            if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
+                // no crossing
+                continue;
+            }
+            if(y1 < y2 && (py >= y1 && py < y2) || // upward
+               y1 > y2 && (py < y1 && py >= y2)) { // downward
+                ++crosses;
+            }
+        }
+        var contained = (crosses == -1) ?
+            // on edge
+            1 :
+            // even (out) or odd (in)
+            !!(crosses & 1);
+
+        return contained;
+    },
+
+    /**
+     * APIMethod: intersects
+     * Determine if the input geometry intersects this one.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+     *
+     * Returns:
+     * {Boolean} The input geometry intersects this one.
+     */
+    intersects: function(geometry) {
+        var intersect = false;
+        if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+            intersect = this.containsPoint(geometry);
+        } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
+            intersect = geometry.intersects(this);
+        } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+            intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply(
+                this, [geometry]
+            );
+        } else {
+            // check for component intersections
+            for(var i=0, len=geometry.components.length; i<len; ++ i) {
+                intersect = geometry.components[i].intersects(this);
+                if(intersect) {
+                    break;
+                }
+            }
+        }
+        return intersect;
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.LinearRing"
+});
+/* ======================================================================
+    OpenLayers/Handler/Path.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Point.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Path
+ * Handler to draw a path on the map.  Path is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Handler.Point>
+ */
+OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, {
+    
+    /**
+     * Property: line
+     * {<OpenLayers.Feature.Vector>}
+     */
+    line: null,
+    
+    /**
+     * Property: freehand
+     * {Boolean} In freehand mode, the handler starts the path on mouse down,
+     * adds a point for every mouse move, and finishes the path on mouse up.
+     * Outside of freehand mode, a point is added to the path on every mouse
+     * click and double-click finishes the path.
+     */
+    freehand: false,
+    
+    /**
+     * Property: freehandToggle
+     * {String} If set, freehandToggle is checked on mouse events and will set
+     * the freehand mode to the opposite of this.freehand.  To disallow
+     * toggling between freehand and non-freehand mode, set freehandToggle to
+     * null.  Acceptable toggle values are 'shiftKey', 'ctrlKey', and 'altKey'.
+     */
+    freehandToggle: 'shiftKey',
+
+    /**
+     * Constructor: OpenLayers.Handler.Path
+     * Create a new path hander
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} 
+     * callbacks - {Object} An object with a 'done' property whos value is a
+     *     function to be called when the path drawing is finished. The 
+     *     callback should expect to recieve a single argument, the line 
+     *     string geometry. If the callbacks object contains a 'point' 
+     *     property, this function will be sent each point as they are added.  
+     *     If the callbacks object contains a 'cancel' property, this function 
+     *     will be called when the handler is deactivated while drawing. The 
+     *     cancel should expect to receive a geometry.
+     * options - {Object} An optional object with properties to be set on the
+     *           handler
+     */
+    initialize: function(control, callbacks, options) {
+        OpenLayers.Handler.Point.prototype.initialize.apply(this, arguments);
+    },
+        
+    /**
+     * Method: createFeature
+     * Add temporary geometries
+     */
+    createFeature: function() {
+        this.line = new OpenLayers.Feature.Vector(
+                                        new OpenLayers.Geometry.LineString());
+        this.point = new OpenLayers.Feature.Vector(
+                                        new OpenLayers.Geometry.Point());
+    },
+        
+    /**
+     * Method: destroyFeature
+     * Destroy temporary geometries
+     */
+    destroyFeature: function() {
+        OpenLayers.Handler.Point.prototype.destroyFeature.apply(this);
+        if(this.line) {
+            this.line.destroy();
+        }
+        this.line = null;
+    },
+    
+    /**
+     * Method: addPoint
+     * Add point to geometry.  Send the point index to override
+     * the behavior of LinearRing that disregards adding duplicate points.
+     */
+    addPoint: function() {
+        this.line.geometry.addComponent(this.point.geometry.clone(),
+                                        this.line.geometry.components.length);
+        this.callback("point", [this.point.geometry]);
+    },
+    
+    /**
+     * Method: freehandMode
+     * Determine whether to behave in freehand mode or not.
+     *
+     * Returns:
+     * {Boolean}
+     */
+    freehandMode: function(evt) {
+        return (this.freehandToggle && evt[this.freehandToggle]) ?
+                    !this.freehand : this.freehand;
+    },
+
+    /**
+     * Method: modifyFeature
+     * Modify the existing geometry given the new point
+     */
+    modifyFeature: function() {
+        var index = this.line.geometry.components.length - 1;
+        this.line.geometry.components[index].x = this.point.geometry.x;
+        this.line.geometry.components[index].y = this.point.geometry.y;
+        this.line.geometry.components[index].clearBounds();
+    },
+    
+    /**
+     * Method: drawFeature
+     * Render geometries on the temporary layer.
+     */
+    drawFeature: function() {
+        this.layer.drawFeature(this.line, this.style);
+        this.layer.drawFeature(this.point, this.style);
+    },
+
+    /**
+     * Method: geometryClone
+     * Return a clone of the relevant geometry.
+     *
+     * Returns:
+     * {<OpenLayers.Geometry.LineString>}
+     */
+    geometryClone: function() {
+        return this.line.geometry.clone();
+    },
+
+    /**
+     * Method: mousedown
+     * Handle mouse down.  Add a new point to the geometry and
+     * render it. Return determines whether to propagate the event on the map.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mousedown: function(evt) {
+        // ignore double-clicks
+        if (this.lastDown && this.lastDown.equals(evt.xy)) {
+            return false;
+        }
+        if(this.lastDown == null) {
+            this.createFeature();
+        }
+        this.mouseDown = true;
+        this.lastDown = evt.xy;
+        var lonlat = this.control.map.getLonLatFromPixel(evt.xy);
+        this.point.geometry.x = lonlat.lon;
+        this.point.geometry.y = lonlat.lat;
+        if((this.lastUp == null) || !this.lastUp.equals(evt.xy)) {
+            this.addPoint();
+        }
+        this.drawFeature();
+        this.drawing = true;
+        return false;
+    },
+
+    /**
+     * Method: mousemove
+     * Handle mouse move.  Adjust the geometry and redraw.
+     * Return determines whether to propagate the event on the map.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mousemove: function (evt) {
+        if(this.drawing) { 
+            var lonlat = this.map.getLonLatFromPixel(evt.xy);
+            this.point.geometry.x = lonlat.lon;
+            this.point.geometry.y = lonlat.lat;
+            this.point.geometry.clearBounds();
+            if(this.mouseDown && this.freehandMode(evt)) {
+                this.addPoint();
+            } else {
+                this.modifyFeature();
+            }
+            this.drawFeature();
+        }
+        return true;
+    },
+    
+    /**
+     * Method: mouseup
+     * Handle mouse up.  Send the latest point in the geometry to
+     * the control. Return determines whether to propagate the event on the map.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mouseup: function (evt) {
+        this.mouseDown = false;
+        if(this.drawing) {
+            if(this.freehandMode(evt)) {
+                this.finalize();
+            } else {
+                if(this.lastUp == null) {
+                   this.addPoint();
+                }
+                this.lastUp = evt.xy;
+            }
+            return false;
+        }
+        return true;
+    },
+  
+    /**
+     * Method: dblclick 
+     * Handle double-clicks.  Finish the geometry and send it back
+     * to the control.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    dblclick: function(evt) {
+        if(!this.freehandMode(evt)) {
+            var index = this.line.geometry.components.length - 1;
+            this.line.geometry.removeComponent(this.line.geometry.components[index]);
+            this.finalize();
+        }
+        return false;
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Path"
+});
+/* ======================================================================
+    OpenLayers/Format/WFS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFS
+ * Read/Write WFS. 
+ */
+OpenLayers.Format.WFS = OpenLayers.Class(OpenLayers.Format.GML, {
+    
+    /** 
+     * Property: layer
+     */
+    layer: null,
+    
+    /**
+     * APIProperty: wfsns
+     */
+    wfsns: "http://www.opengis.net/wfs",
+    
+    /**
+     * Property: ogcns
+     */
+    ogcns: "http://www.opengis.net/ogc",
+    
+    /*
+     * Constructor: OpenLayers.Format.WFS
+     * Create a WFS-T formatter. This requires a layer: that layer should
+     * have two properties: geometry_column and typename. The parser
+     * for this format is subclassed entirely from GML: There is a writer 
+     * only, which uses most of the code from the GML layer, and wraps
+     * it in transactional elements.
+     * 
+     * Parameters: 
+     * options - {Object} 
+     * layer - {<OpenLayers.Layer>} 
+     */
+    
+    initialize: function(options, layer) {
+        OpenLayers.Format.GML.prototype.initialize.apply(this, [options]);
+        this.layer = layer;
+        if (this.layer.featureNS) {
+            this.featureNS = this.layer.featureNS;
+        }    
+        if (this.layer.options.geometry_column) {
+            this.geometryName = this.layer.options.geometry_column;
+        }
+        if (this.layer.options.typename) {
+            this.featureName = this.layer.options.typename;
+        }
+    },
+    
+    /**
+     * Method: write 
+     * Takes a feature list, and generates a WFS-T Transaction 
+     *
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    write: function(features) {
+    
+        var transaction = this.createElementNS(this.wfsns, 'wfs:Transaction');
+        transaction.setAttribute("version","1.0.0");
+        transaction.setAttribute("service","WFS");
+        for (var i=0; i < features.length; i++) {
+            switch (features[i].state) {
+                case OpenLayers.State.INSERT:
+                    transaction.appendChild(this.insert(features[i]));
+                    break;
+                case OpenLayers.State.UPDATE:
+                    transaction.appendChild(this.update(features[i]));
+                    break;
+                case OpenLayers.State.DELETE:
+                    transaction.appendChild(this.remove(features[i]));
+                    break;
+            }
+        }
+        
+        return OpenLayers.Format.XML.prototype.write.apply(this,[transaction]);
+    },
+   
+    /**
+     * Method: createFeatureXML
+     *
+     * Parameters: 
+     * feature - {<OpenLayers.Feature.Vector>}
+     */ 
+    createFeatureXML: function(feature) {
+        var geometryNode = this.buildGeometryNode(feature.geometry);
+        var geomContainer = this.createElementNS(this.featureNS, "feature:" + this.geometryName);
+        geomContainer.appendChild(geometryNode);
+        var featureContainer = this.createElementNS(this.featureNS, "feature:" + this.featureName);
+        featureContainer.appendChild(geomContainer);
+        for(var attr in feature.attributes) {
+            var attrText = this.createTextNode(feature.attributes[attr]); 
+            var nodename = attr;
+            if (attr.search(":") != -1) {
+                nodename = attr.split(":")[1];
+            }    
+            var attrContainer = this.createElementNS(this.featureNS, "feature:" + nodename);
+            attrContainer.appendChild(attrText);
+            featureContainer.appendChild(attrContainer);
+        }    
+        return featureContainer;
+    },
+    
+    /**
+     * Method: insert 
+     * Takes a feature, and generates a WFS-T Transaction "Insert" 
+     *
+     * Parameters: 
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    insert: function(feature) {
+        var insertNode = this.createElementNS(this.wfsns, 'wfs:Insert');
+        insertNode.appendChild(this.createFeatureXML(feature));
+        return insertNode;
+    },
+    
+    /**
+     * Method: update
+     * Takes a feature, and generates a WFS-T Transaction "Update" 
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    update: function(feature) {
+        if (!feature.fid) { OpenLayers.Console.userError(OpenLayers.i18n("noFID")); }
+        var updateNode = this.createElementNS(this.wfsns, 'wfs:Update');
+        updateNode.setAttribute("typeName", this.layerName);
+
+        var propertyNode = this.createElementNS(this.wfsns, 'wfs:Property');
+        var nameNode = this.createElementNS(this.wfsns, 'wfs:Name');
+        
+        var txtNode = this.createTextNode(this.geometryName);
+        nameNode.appendChild(txtNode);
+        propertyNode.appendChild(nameNode);
+        
+        var valueNode = this.createElementNS(this.wfsns, 'wfs:Value');
+        
+        var geometryNode = this.buildGeometryNode(feature.geometry);
+        
+        if(feature.layer){
+            geometryNode.setAttribute(
+                "srsName", feature.layer.projection.getCode()
+            );
+        }
+        
+        valueNode.appendChild(geometryNode);
+        
+        propertyNode.appendChild(valueNode);
+        updateNode.appendChild(propertyNode);
+        
+         // add in attributes
+        for(var propName in feature.attributes) {
+            propertyNode = this.createElementNS(this.wfsns, 'wfs:Property');
+            nameNode = this.createElementNS(this.wfsns, 'wfs:Name');
+            nameNode.appendChild(this.createTextNode(propName));
+            propertyNode.appendChild(nameNode);
+            valueNode = this.createElementNS(this.wfsns, 'wfs:Value');
+            valueNode.appendChild(this.createTextNode(feature.attributes[propName]));
+            propertyNode.appendChild(valueNode);
+            updateNode.appendChild(propertyNode);
+        }
+        
+        
+        var filterNode = this.createElementNS(this.ogcns, 'ogc:Filter');
+        var filterIdNode = this.createElementNS(this.ogcns, 'ogc:FeatureId');
+        filterIdNode.setAttribute("fid", feature.fid);
+        filterNode.appendChild(filterIdNode);
+        updateNode.appendChild(filterNode);
+
+        return updateNode;
+    },
+    
+    /**
+     * Method: remove 
+     * Takes a feature, and generates a WFS-T Transaction "Delete" 
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    remove: function(feature) {
+        if (!feature.fid) { 
+            OpenLayers.Console.userError(OpenLayers.i18n("noFID")); 
+            return false; 
+        }
+        var deleteNode = this.createElementNS(this.wfsns, 'wfs:Delete');
+        deleteNode.setAttribute("typeName", this.layerName);
+
+        var filterNode = this.createElementNS(this.ogcns, 'ogc:Filter');
+        var filterIdNode = this.createElementNS(this.ogcns, 'ogc:FeatureId');
+        filterIdNode.setAttribute("fid", feature.fid);
+        filterNode.appendChild(filterIdNode);
+        deleteNode.appendChild(filterNode);
+
+        return deleteNode;
+    },
+
+    /**
+     * APIMethod: destroy
+     * Remove ciruclar ref to layer 
+     */
+    destroy: function() {
+        this.layer = null;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WFS" 
+});    
+/* ======================================================================
+    OpenLayers/Handler/Polygon.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Polygon
+ * Handler to draw a polygon on the map.  Polygon is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Handler.Path>
+ *  - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, {
+    
+    /**
+     * Parameter: polygon
+     * {<OpenLayers.Feature.Vector>}
+     */
+    polygon: null,
+
+    /**
+     * Constructor: OpenLayers.Handler.Polygon
+     * Create a Polygon Handler.
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} 
+     * callbacks - {Object} An object with a 'done' property whos value is
+     *                          a function to be called when the path drawing is
+     *                          finished. The callback should expect to recieve a
+     *                          single argument, the polygon geometry.
+     *                          If the callbacks object contains a 'point'
+     *                          property, this function will be sent each point
+     *                          as they are added.  If the callbacks object contains
+     *                          a 'cancel' property, this function will be called when
+     *                          the handler is deactivated while drawing.  The cancel
+     *                          should expect to receive a geometry.
+     * options - {Object} 
+     */
+    initialize: function(control, callbacks, options) {
+        OpenLayers.Handler.Path.prototype.initialize.apply(this, arguments);
+    },
+    
+    /**
+     * Method: createFeature
+     * Add temporary geometries
+     */
+    createFeature: function() {
+        this.polygon = new OpenLayers.Feature.Vector(
+                                        new OpenLayers.Geometry.Polygon());
+        this.line = new OpenLayers.Feature.Vector(
+                                        new OpenLayers.Geometry.LinearRing());
+        this.polygon.geometry.addComponent(this.line.geometry);
+        this.point = new OpenLayers.Feature.Vector(
+                                        new OpenLayers.Geometry.Point());
+    },
+
+    /**
+     * Method: destroyFeature
+     * Destroy temporary geometries
+     */
+    destroyFeature: function() {
+        OpenLayers.Handler.Path.prototype.destroyFeature.apply(this);
+        if(this.polygon) {
+            this.polygon.destroy();
+        }
+        this.polygon = null;
+    },
+
+    /**
+     * Method: modifyFeature
+     * Modify the existing geometry given the new point
+     * 
+     */
+    modifyFeature: function() {
+        var index = this.line.geometry.components.length - 2;
+        this.line.geometry.components[index].x = this.point.geometry.x;
+        this.line.geometry.components[index].y = this.point.geometry.y;
+        this.line.geometry.components[index].clearBounds();
+    },
+
+    /**
+     * Method: drawFeature
+     * Render geometries on the temporary layer.
+     */
+    drawFeature: function() {
+        this.layer.drawFeature(this.polygon, this.style);
+        this.layer.drawFeature(this.point, this.style);
+    },
+
+    /**
+     * Method: geometryClone
+     * Return a clone of the relevant geometry.
+     *
+     * Returns:
+     * {<OpenLayers.Geometry.Polygon>}
+     */
+    geometryClone: function() {
+        return this.polygon.geometry.clone();
+    },
+
+    /**
+     * Method: dblclick
+     * Handle double-clicks.  Finish the geometry and send it back
+     * to the control.
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    dblclick: function(evt) {
+        if(!this.freehandMode(evt)) {
+            // remove the penultimate point
+            var index = this.line.geometry.components.length - 2;
+            this.line.geometry.removeComponent(this.line.geometry.components[index]);
+            this.finalize();
+        }
+        return false;
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Polygon"
+});
+/* ======================================================================
+    OpenLayers/Control/EditingToolbar.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/Navigation.js
+ * @requires OpenLayers/Control/DrawFeature.js
+ * @requires OpenLayers/Handler/Point.js
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Handler/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Control.EditingToolbar 
+ */
+OpenLayers.Control.EditingToolbar = OpenLayers.Class(
+  OpenLayers.Control.Panel, {
+
+    /**
+     * Constructor: OpenLayers.Control.EditingToolbar
+     * Create an editing toolbar for a given layer. 
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.Vector>} 
+     * options - {Object} 
+     */
+    initialize: function(layer, options) {
+        OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+        
+        this.addControls(
+          [ new OpenLayers.Control.Navigation() ]
+        );  
+        var controls = [
+          new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint'}),
+          new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'}),
+          new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'})
+        ];
+        for (var i=0, len=controls.length; i<len; i++) {
+            controls[i].featureAdded = function(feature) { feature.state = OpenLayers.State.INSERT; };
+        }
+        this.addControls(controls);
+    },
+
+    /**
+     * Method: draw
+     * calls the default draw, and then activates mouse defaults.
+     *
+     * Returns:
+     * {DOMElement}
+     */
+    draw: function() {
+        var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments);
+        this.activateControl(this.controls[0]);
+        return div;
+    },
+
+    CLASS_NAME: "OpenLayers.Control.EditingToolbar"
+});    

Modified: trunk/lib/OpenLayers/OpenLayersCompressed.js
===================================================================
--- trunk/lib/OpenLayers/OpenLayersCompressed.js	2008-09-05 14:31:06 UTC (rev 1498)
+++ trunk/lib/OpenLayers/OpenLayersCompressed.js	2008-09-05 14:39:19 UTC (rev 1499)
@@ -43,41 +43,27 @@
 *
 **/
 
-var OpenLayers={singleFile:true};(function(){var singleFile=(typeof OpenLayers=="object"&&OpenLayers.singleFile);window.OpenLayers={_scriptName:(!singleFile)?"lib/OpenLayers.js":"OpenLayers.js",_getScriptLocation:function(){var scriptLocation="";var scriptName=OpenLayers._scriptName;var scripts=document.getElementsByTagName('script');for(var i=0;i<scripts.length;i++){var src=scripts[i].getAttribute('src');if(src){var index=src.lastIndexOf(scriptName);var pathLength=src.lastIndexOf('?');if(pathLength<0){pathLength=src.length;}
+/**
+ * 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
+ */
+var OpenLayers={singleFile:true};(function(){var singleFile=(typeof OpenLayers=="object"&&OpenLayers.singleFile);window.OpenLayers={_scriptName:(!singleFile)?"lib/OpenLayers.js":"OpenLayers.js",_getScriptLocation:function(){var scriptLocation="";var scriptName=OpenLayers._scriptName;var scripts=document.getElementsByTagName('script');for(var i=0,len=scripts.length;i<len;i++){var src=scripts[i].getAttribute('src');if(src){var index=src.lastIndexOf(scriptName);var pathLength=src.lastIndexOf('?');if(pathLength<0){pathLength=src.length;}
 if((index>-1)&&(index+scriptName.length==pathLength)){scriptLocation=src.slice(0,pathLength-scriptName.length);break;}}}
-return scriptLocation;}};if(!singleFile){var jsfiles=new Array("OpenLayers/Util.js","OpenLayers/BaseTypes.js","OpenLayers/BaseTypes/Class.js","OpenLayers/BaseTypes/Bounds.js","OpenLayers/BaseTypes/Element.js","OpenLayers/BaseTypes/LonLat.js","OpenLayers/BaseTypes/Pixel.js","OpenLayers/BaseTypes/Size.js","OpenLayers/Console.js","OpenLayers/Tween.js","Rico/Corner.js","Rico/Color.js","OpenLayers/Ajax.js","OpenLayers/Events.js","OpenLayers/Projection.js","OpenLayers/Map.js","OpenLayers/Layer.js","OpenLayers/Icon.js","OpenLayers/Marker.js","OpenLayers/Marker/Box.js","OpenLayers/Popup.js","OpenLayers/Tile.js","OpenLayers/Tile/Image.js","OpenLayers/Tile/WFS.js","OpenLayers/Layer/Image.js","OpenLayers/Layer/SphericalMercator.js","OpenLayers/Layer/EventPane.js","OpenLayers/Layer/FixedZoomLevels.js","OpenLayers/Layer/Google.js","OpenLayers/Layer/VirtualEarth.js","OpenLayers/Layer/Yahoo.js","OpenLayers/Layer/HTTPRequest.js","OpenLayers/Layer/Grid.js","OpenLayers/Layer/MapGuide.js","OpenLayers/Layer/MapServer.js","OpenLayers/Layer/MapServer/Untiled.js","OpenLayers/Layer/KaMap.js","OpenLayers/Layer/MultiMap.js","OpenLayers/Layer/Markers.js","OpenLayers/Layer/Text.js","OpenLayers/Layer/WorldWind.js","OpenLayers/Layer/WMS.js","OpenLayers/Layer/WMS/Untiled.js","OpenLayers/Layer/GeoRSS.js","OpenLayers/Layer/Boxes.js","OpenLayers/Layer/TMS.js","OpenLayers/Layer/TileCache.js","OpenLayers/Popup/Anchored.js","OpenLayers/Popup/AnchoredBubble.js","OpenLayers/Popup/Framed.js","OpenLayers/Popup/FramedCloud.js","OpenLayers/Feature.js","OpenLayers/Feature/Vector.js","OpenLayers/Feature/WFS.js","OpenLayers/Handler.js","OpenLayers/Handler/Click.js","OpenLayers/Handler/Hover.js","OpenLayers/Handler/Point.js","OpenLayers/Handler/Path.js","OpenLayers/Handler/Polygon.js","OpenLayers/Handler/Feature.js","OpenLayers/Handler/Drag.js","OpenLayers/Handler/RegularPolygon.js","OpenLayers/Handler/Box.js","OpenLayers/Handler/MouseWheel.js","OpenLayers/Handler/Keyboard.js","OpenLayers/Control.js","OpenLayers/Control/Attribution.js","OpenLayers/Control/Button.js","OpenLayers/Control/ZoomBox.js","OpenLayers/Control/ZoomToMaxExtent.js","OpenLayers/Control/DragPan.js","OpenLayers/Control/Navigation.js","OpenLayers/Control/MouseDefaults.js","OpenLayers/Control/MousePosition.js","OpenLayers/Control/OverviewMap.js","OpenLayers/Control/KeyboardDefaults.js","OpenLayers/Control/PanZoom.js","OpenLayers/Control/PanZoomBar.js","OpenLayers/Control/ArgParser.js","OpenLayers/Control/Permalink.js","OpenLayers/Control/Scale.js","OpenLayers/Control/ScaleLine.js","OpenLayers/Control/LayerSwitcher.js","OpenLayers/Control/DrawFeature.js","OpenLayers/Control/DragFeature.js","OpenLayers/Control/ModifyFeature.js","OpenLayers/Control/Panel.js","OpenLayers/Control/SelectFeature.js","OpenLayers/Control/NavigationHistory.js","OpenLayers/Geometry.js","OpenLayers/Geometry/Rectangle.js","OpenLayers/Geometry/Collection.js","OpenLayers/Geometry/Point.js","OpenLayers/Geometry/MultiPoint.js","OpenLayers/Geometry/Curve.js","OpenLayers/Geometry/LineString.js","OpenLayers/Geometry/LinearRing.js","OpenLayers/Geometry/Polygon.js","OpenLayers/Geometry/MultiLineString.js","OpenLayers/Geometry/MultiPolygon.js","OpenLayers/Geometry/Surface.js","OpenLayers/Renderer.js","OpenLayers/Renderer/Elements.js","OpenLayers/Renderer/SVG.js","OpenLayers/Renderer/VML.js","OpenLayers/Layer/Vector.js","OpenLayers/Layer/PointTrack.js","OpenLayers/Layer/GML.js","OpenLayers/Style.js","OpenLayers/StyleMap.js","OpenLayers/Rule.js","OpenLayers/Filter.js","OpenLayers/Filter/FeatureId.js","OpenLayers/Filter/Logical.js","OpenLayers/Filter/Comparison.js","OpenLayers/Format.js","OpenLayers/Format/XML.js","OpenLayers/Format/GML.js","OpenLayers/Format/KML.js","OpenLayers/Format/GeoRSS.js","OpenLayers/Format/WFS.js","OpenLayers/Format/WKT.js","OpenLayers/Format/OSM.js","OpenLayers/Format/SLD.js","OpenLayers/Format/SLD/v1.js","OpenLayers/Format/SLD/v1_0_0.js","OpenLayers/Format/Text.js","OpenLayers/Format/JSON.js","OpenLayers/Format/GeoJSON.js","OpenLayers/Format/WMC.js","OpenLayers/Format/WMC/v1.js","OpenLayers/Format/WMC/v1_0_0.js","OpenLayers/Format/WMC/v1_1_0.js","OpenLayers/Layer/WFS.js","OpenLayers/Control/MouseToolbar.js","OpenLayers/Control/NavToolbar.js","OpenLayers/Control/EditingToolbar.js","OpenLayers/Lang.js","OpenLayers/Lang/en.js");var agent=navigator.userAgent;var docWrite=(agent.match("MSIE")||agent.match("Safari"));if(docWrite){var allScriptTags=new Array(jsfiles.length);}
-var host=OpenLayers._getScriptLocation()+"lib/";for(var i=0;i<jsfiles.length;i++){if(docWrite){allScriptTags[i]="<script src='"+host+jsfiles[i]+"'></script>";}else{var s=document.createElement("script");s.src=host+jsfiles[i];var h=document.getElementsByTagName("head").length?document.getElementsByTagName("head")[0]:document.body;h.appendChild(s);}}
-if(docWrite){document.write(allScriptTags.join(""));}}})();OpenLayers.VERSION_NUMBER="$Revision$";OpenLayers.String={startsWith:function(str,sub){return(str.indexOf(sub)==0);},contains:function(str,sub){return(str.indexOf(sub)!=-1);},trim:function(str){return str.replace(/^\s*(.*?)\s*$/,"$1");},camelize:function(str){var oStringList=str.split('-');var camelizedString=oStringList[0];for(var i=1;i<oStringList.length;i++){var s=oStringList[i];camelizedString+=s.charAt(0).toUpperCase()+s.substring(1);}
-return camelizedString;},format:function(template,context,args){if(!context){context=window;}
-var tokens=template.split("${");var item,last,replacement;for(var i=1;i<tokens.length;i++){item=tokens[i];last=item.indexOf("}");if(last>0){replacement=context[item.substring(0,last)];if(typeof replacement=="function"){replacement=args?replacement.apply(null,args):replacement();}
-tokens[i]=replacement+item.substring(++last);}else{tokens[i]="${"+item;}}
-return tokens.join("");}};if(!String.prototype.startsWith){String.prototype.startsWith=function(sStart){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.startsWith'}));return OpenLayers.String.startsWith(this,sStart);};}
-if(!String.prototype.contains){String.prototype.contains=function(str){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.contains'}));return OpenLayers.String.contains(this,str);};}
-if(!String.prototype.trim){String.prototype.trim=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.trim'}));return OpenLayers.String.trim(this);};}
-if(!String.prototype.camelize){String.prototype.camelize=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.camelize'}));return OpenLayers.String.camelize(this);};}
-OpenLayers.Number={decimalSeparator:".",thousandsSeparator:",",limitSigDigs:function(num,sig){var fig=0;if(sig>0){fig=parseFloat(num.toPrecision(sig));}
-return fig;},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){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;}};if(!Number.prototype.limitSigDigs){Number.prototype.limitSigDigs=function(sig){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.limitSigDigs'}));return OpenLayers.Number.limitSigDigs(this,sig);};}
-OpenLayers.Function={bind:function(func,object){var args=Array.prototype.slice.apply(arguments,[2]);return function(){var newArgs=args.concat(Array.prototype.slice.apply(arguments,[0]));return func.apply(object,newArgs);};},bindAsEventListener:function(func,object){return function(event){return func.call(object,event||window.event);};}};if(!Function.prototype.bind){Function.prototype.bind=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.bind'}));Array.prototype.unshift.apply(arguments,[this]);return OpenLayers.Function.bind.apply(null,arguments);};}
-if(!Function.prototype.bindAsEventListener){Function.prototype.bindAsEventListener=function(object){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.bindAsEventListener'}));return OpenLayers.Function.bindAsEventListener(this,object);};}
-OpenLayers.Array={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.Class=function(){var Class=function(){if(arguments&&arguments[0]!=OpenLayers.Class.isPrototype){this.initialize.apply(this,arguments);}};var extended={};var parent;for(var i=0;i<arguments.length;++i){if(typeof arguments[i]=="function"){parent=arguments[i].prototype;}else{parent=arguments[i];}
-OpenLayers.Util.extend(extended,parent);}
-Class.prototype=extended;return Class;};OpenLayers.Class.isPrototype=function(){};OpenLayers.Class.create=function(){return function(){if(arguments&&arguments[0]!=OpenLayers.Class.isPrototype){this.initialize.apply(this,arguments);}};};OpenLayers.Class.inherit=function(){var superClass=arguments[0];var proto=new superClass(OpenLayers.Class.isPrototype);for(var i=1;i<arguments.length;i++){if(typeof arguments[i]=="function"){var mixin=arguments[i];arguments[i]=new mixin(OpenLayers.Class.isPrototype);}
-OpenLayers.Util.extend(proto,arguments[i]);}
-return proto;};OpenLayers.Util={};OpenLayers.Util.getElement=function(){var elements=[];for(var i=0;i<arguments.length;i++){var element=arguments[i];if(typeof element=='string'){element=document.getElementById(element);}
+return scriptLocation;}};if(!singleFile){var jsfiles=new Array("OpenLayers/Util.js","OpenLayers/BaseTypes.js","OpenLayers/BaseTypes/Class.js","OpenLayers/BaseTypes/Bounds.js","OpenLayers/BaseTypes/Element.js","OpenLayers/BaseTypes/LonLat.js","OpenLayers/BaseTypes/Pixel.js","OpenLayers/BaseTypes/Size.js","OpenLayers/Console.js","OpenLayers/Tween.js","Rico/Corner.js","Rico/Color.js","OpenLayers/Ajax.js","OpenLayers/Request.js","OpenLayers/Request/XMLHttpRequest.js","OpenLayers/Events.js","OpenLayers/Projection.js","OpenLayers/Map.js","OpenLayers/Layer.js","OpenLayers/Icon.js","OpenLayers/Marker.js","OpenLayers/Marker/Box.js","OpenLayers/Popup.js","OpenLayers/Tile.js","OpenLayers/Tile/Image.js","OpenLayers/Tile/WFS.js","OpenLayers/Layer/Image.js","OpenLayers/Layer/SphericalMercator.js","OpenLayers/Layer/EventPane.js","OpenLayers/Layer/FixedZoomLevels.js","OpenLayers/Layer/Google.js","OpenLayers/Layer/VirtualEarth.js","OpenLayers/Layer/Yahoo.js","OpenLayers/Layer/HTTPRequest.js","OpenLayers/Layer/Grid.js","OpenLayers/Layer/MapGuide.js","OpenLayers/Layer/MapServer.js","OpenLayers/Layer/MapServer/Untiled.js","OpenLayers/Layer/KaMap.js","OpenLayers/Layer/MultiMap.js","OpenLayers/Layer/Markers.js","OpenLayers/Layer/Text.js","OpenLayers/Layer/WorldWind.js","OpenLayers/Layer/WMS.js","OpenLayers/Layer/WMS/Untiled.js","OpenLayers/Layer/GeoRSS.js","OpenLayers/Layer/Boxes.js","OpenLayers/Layer/TMS.js","OpenLayers/Layer/TileCache.js","OpenLayers/Popup/Anchored.js","OpenLayers/Popup/AnchoredBubble.js","OpenLayers/Popup/Framed.js","OpenLayers/Popup/FramedCloud.js","OpenLayers/Feature.js","OpenLayers/Feature/Vector.js","OpenLayers/Feature/WFS.js","OpenLayers/Handler.js","OpenLayers/Handler/Click.js","OpenLayers/Handler/Hover.js","OpenLayers/Handler/Point.js","OpenLayers/Handler/Path.js","OpenLayers/Handler/Polygon.js","OpenLayers/Handler/Feature.js","OpenLayers/Handler/Drag.js","OpenLayers/Handler/RegularPolygon.js","OpenLayers/Handler/Box.js","OpenLayers/Handler/MouseWheel.js","OpenLayers/Handler/Keyboard.js","OpenLayers/Control.js","OpenLayers/Control/Attribution.js","OpenLayers/Control/Button.js","OpenLayers/Control/ZoomBox.js","OpenLayers/Control/ZoomToMaxExtent.js","OpenLayers/Control/DragPan.js","OpenLayers/Control/Navigation.js","OpenLayers/Control/MouseDefaults.js","OpenLayers/Control/MousePosition.js","OpenLayers/Control/OverviewMap.js","OpenLayers/Control/KeyboardDefaults.js","OpenLayers/Control/PanZoom.js","OpenLayers/Control/PanZoomBar.js","OpenLayers/Control/ArgParser.js","OpenLayers/Control/Permalink.js","OpenLayers/Control/Scale.js","OpenLayers/Control/ScaleLine.js","OpenLayers/Control/LayerSwitcher.js","OpenLayers/Control/DrawFeature.js","OpenLayers/Control/DragFeature.js","OpenLayers/Control/ModifyFeature.js","OpenLayers/Control/Panel.js","OpenLayers/Control/SelectFeature.js","OpenLayers/Control/NavigationHistory.js","OpenLayers/Geometry.js","OpenLayers/Geometry/Rectangle.js","OpenLayers/Geometry/Collection.js","OpenLayers/Geometry/Point.js","OpenLayers/Geometry/MultiPoint.js","OpenLayers/Geometry/Curve.js","OpenLayers/Geometry/LineString.js","OpenLayers/Geometry/LinearRing.js","OpenLayers/Geometry/Polygon.js","OpenLayers/Geometry/MultiLineString.js","OpenLayers/Geometry/MultiPolygon.js","OpenLayers/Geometry/Surface.js","OpenLayers/Renderer.js","OpenLayers/Renderer/Elements.js","OpenLayers/Renderer/SVG.js","OpenLayers/Renderer/Canvas.js","OpenLayers/Renderer/VML.js","OpenLayers/Layer/Vector.js","OpenLayers/Strategy.js","OpenLayers/Strategy/Fixed.js","OpenLayers/Protocol.js","OpenLayers/Layer/PointTrack.js","OpenLayers/Layer/GML.js","OpenLayers/Style.js","OpenLayers/StyleMap.js","OpenLayers/Rule.js","OpenLayers/Filter.js","OpenLayers/Filter/FeatureId.js","OpenLayers/Filter/Logical.js","OpenLayers/Filter/Comparison.js","OpenLayers/Format.js","OpenLayers/Format/XML.js","OpenLayers/Format/GML.js","OpenLayers/Format/KML.js","OpenLayers/Format/GeoRSS.js","OpenLayers/Format/WFS.js","OpenLayers/Format/WKT.js","OpenLayers/Format/OSM.js","OpenLayers/Format/GPX.js","OpenLayers/Format/SLD.js","OpenLayers/Format/SLD/v1.js","OpenLayers/Format/SLD/v1_0_0.js","OpenLayers/Format/SLD/v1.js","OpenLayers/Format/Filter.js","OpenLayers/Format/Filter/v1.js","OpenLayers/Format/Filter/v1_0_0.js","OpenLayers/Format/Text.js","OpenLayers/Format/JSON.js","OpenLayers/Format/GeoJSON.js","OpenLayers/Format/WMC.js","OpenLayers/Format/WMC/v1.js","OpenLayers/Format/WMC/v1_0_0.js","OpenLayers/Format/WMC/v1_1_0.js","OpenLayers/Layer/WFS.js","OpenLayers/Control/MouseToolbar.js","OpenLayers/Control/NavToolbar.js","OpenLayers/Control/EditingToolbar.js","OpenLayers/Lang.js","OpenLayers/Lang/en.js");var agent=navigator.userAgent;var docWrite=(agent.match("MSIE")||agent.match("Safari"));if(docWrite){var allScriptTags=new Array(jsfiles.length);}
+var host=OpenLayers._getScriptLocation()+"lib/";for(var i=0,len=jsfiles.length;i<len;i++){if(docWrite){allScriptTags[i]="<script src='"+host+jsfiles[i]+"'></script>";}else{var s=document.createElement("script");s.src=host+jsfiles[i];var h=document.getElementsByTagName("head").length?document.getElementsByTagName("head")[0]:document.body;h.appendChild(s);}}
+if(docWrite){document.write(allScriptTags.join(""));}}})();OpenLayers.VERSION_NUMBER="$Revision$";OpenLayers.Util={};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;};if($==null){var $=OpenLayers.Util.getElement;}
-OpenLayers.Util.extend=function(destination,source){if(destination&&source){for(var property in source){var value=source[property];if(value!==undefined){destination[property]=value;}}
+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;}}
 var sourceIsEvt=typeof window.Event=="function"&&source instanceof window.Event;if(!sourceIsEvt&&source.hasOwnProperty&&source.hasOwnProperty('toString')){destination.toString=source.toString;}}
 return destination;};OpenLayers.Util.removeItem=function(array,item){for(var i=array.length-1;i>=0;i--){if(array[i]==item){array.splice(i,1);}}
-return array;};OpenLayers.Util.clearArray=function(array){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'array = []'}));array.length=0;};OpenLayers.Util.indexOf=function(array,obj){for(var i=0;i<array.length;i++){if(array[i]==obj){return i;}}
+return array;};OpenLayers.Util.clearArray=function(array){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'array = []'}));array.length=0;};OpenLayers.Util.indexOf=function(array,obj){for(var i=0,len=array.length;i<len;i++){if(array[i]==obj){return i;}}
 return-1;};OpenLayers.Util.modifyDOMElement=function(element,id,px,sz,position,border,overflow,opacity){if(id){element.id=id;}
 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";}
@@ -91,21 +77,26 @@
 if(!position){position="relative";}
 OpenLayers.Util.modifyDOMElement(image,id,px,sz,position,border,null,opacity);if(delayDisplay){image.style.display="none";OpenLayers.Event.observe(image,"load",OpenLayers.Function.bind(OpenLayers.Util.onImageLoad,image));OpenLayers.Event.observe(image,"error",OpenLayers.Function.bind(OpenLayers.Util.onImageLoadError,image));}
 image.style.alt=id;image.galleryImg="no";if(imgURL){image.src=imgURL;}
-return image;};OpenLayers.Util.setOpacity=function(element,opacity){OpenLayers.Util.modifyDOMElement(element,null,null,null,null,null,null,opacity);};OpenLayers.Util.onImageLoad=function(){if(!this.viewRequestID||(this.map&&this.viewRequestID==this.map.viewRequestID)){this.style.backgroundColor=null;this.style.display="";}};OpenLayers.Util.onImageLoadErrorColor="pink";OpenLayers.IMAGE_RELOAD_ATTEMPTS=0;OpenLayers.Util.onImageLoadError=function(){this._attempts=(this._attempts)?(this._attempts+1):1;if(this._attempts<=OpenLayers.IMAGE_RELOAD_ATTEMPTS){this.src=this.src;}else{this.style.backgroundColor=OpenLayers.Util.onImageLoadErrorColor;}
+return image;};OpenLayers.Util.setOpacity=function(element,opacity){OpenLayers.Util.modifyDOMElement(element,null,null,null,null,null,null,opacity);};OpenLayers.Util.onImageLoad=function(){if(!this.viewRequestID||(this.map&&this.viewRequestID==this.map.viewRequestID)){this.style.backgroundColor=null;this.style.display="";}};OpenLayers.Util.onImageLoadErrorColor="pink";OpenLayers.IMAGE_RELOAD_ATTEMPTS=0;OpenLayers.Util.onImageLoadError=function(){this._attempts=(this._attempts)?(this._attempts+1):1;if(this._attempts<=OpenLayers.IMAGE_RELOAD_ATTEMPTS){var urls=this.urls;if(urls&&urls instanceof Array&&urls.length>1){var src=this.src.toString();var current_url,k;for(k=0;current_url=urls[k];k++){if(src.indexOf(current_url)!=-1){break;}}
+var guess=Math.floor(urls.length*Math.random())
+var new_url=urls[guess];k=0;while(new_url==current_url&&k++<4){guess=Math.floor(urls.length*Math.random())
+new_url=urls[guess];}
+this.src=src.replace(current_url,new_url);}else{this.src=this.src;}}else{this.style.backgroundColor=OpenLayers.Util.onImageLoadErrorColor;}
 this.style.display="";};OpenLayers.Util.alphaHack=function(){var arVersion=navigator.appVersion.split("MSIE");var version=parseFloat(arVersion[1]);var filter=false;try{filter=!!(document.body.filters);}catch(e){}
-return(filter&&(version>=5.5)&&(version<7));};OpenLayers.Util.modifyAlphaImageDiv=function(div,id,px,sz,imgURL,position,border,sizing,opacity){OpenLayers.Util.modifyDOMElement(div,id,px,sz,null,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()){div.style.display="inline-block";if(sizing==null){sizing="scale";}
+return(filter&&(version>=5.5)&&(version<7));};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)";}};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,false);div.appendChild(img);if(delayDisplay){img.style.display="none";OpenLayers.Event.observe(img,"load",OpenLayers.Function.bind(OpenLayers.Util.onImageLoad,div));OpenLayers.Event.observe(img,"error",OpenLayers.Function.bind(OpenLayers.Util.onImageLoadError,div));}
 OpenLayers.Util.modifyAlphaImageDiv(div,id,px,sz,imgURL,position,border,sizing,opacity);return div;};OpenLayers.Util.upperCaseObject=function(object){var uObject={};for(var key in object){uObject[key.toUpperCase()]=object[key];}
-return uObject;};OpenLayers.Util.applyDefaults=function(to,from){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];}}
+return uObject;};OpenLayers.Util.applyDefaults=function(to,from){to=to||{};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];}}
 if(!fromIsEvt&&from.hasOwnProperty&&from.hasOwnProperty('toString')&&!to.hasOwnProperty('toString')){to.toString=from.toString;}
-return to;};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){var encodedItemArray=[];for(var itemIndex=0;itemIndex<value.length;itemIndex++){encodedItemArray.push(encodeURIComponent(value[itemIndex]));}
+return to;};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){var encodedItemArray=[];for(var itemIndex=0,len=value.length;itemIndex<len;itemIndex++){encodedItemArray.push(encodeURIComponent(value[itemIndex]));}
 encodedValue=encodedItemArray.join(",");}
 else{encodedValue=encodeURIComponent(value);}
 paramsArray.push(encodeURIComponent(key)+"="+encodedValue);}}
-return paramsArray.join("&");};OpenLayers.ImgPath='';OpenLayers.Util.getImagesLocation=function(){return OpenLayers.ImgPath||(OpenLayers._getScriptLocation()+"img/");};OpenLayers.Util.Try=function(){var returnValue=null;for(var i=0;i<arguments.length;i++){var lambda=arguments[i];try{returnValue=lambda();break;}catch(e){}}
-return returnValue;};OpenLayers.Util.getNodes=function(p,tagName){var nodes=OpenLayers.Util.Try(function(){return OpenLayers.Util._getNodes(p.documentElement.childNodes,tagName);},function(){return OpenLayers.Util._getNodes(p.childNodes,tagName);});return nodes;};OpenLayers.Util._getNodes=function(nodes,tagName){var retArray=[];for(var i=0;i<nodes.length;i++){if(nodes[i].nodeName==tagName){retArray.push(nodes[i]);}}
+return paramsArray.join("&");};OpenLayers.ImgPath='';OpenLayers.Util.getImagesLocation=function(){return OpenLayers.ImgPath||(OpenLayers._getScriptLocation()+"img/");};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;};OpenLayers.Util.getNodes=function(p,tagName){var nodes=OpenLayers.Util.Try(function(){return OpenLayers.Util._getNodes(p.documentElement.childNodes,tagName);},function(){return OpenLayers.Util._getNodes(p.childNodes,tagName);});return nodes;};OpenLayers.Util._getNodes=function(nodes,tagName){var retArray=[];for(var i=0,len=nodes.length;i<len;i++){if(nodes[i].nodeName==tagName){retArray.push(nodes[i]);}}
 return retArray;};OpenLayers.Util.getTagText=function(parent,item,index){var result=OpenLayers.Util.getNodes(parent,item);if(result&&(result.length>0))
 {if(!index){index=0;}
 if(result[index].childNodes.length>1){return result.childNodes[1].nodeValue;}
@@ -117,16 +108,16 @@
 if(iterLimit==0){return NaN;}
 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;return d;};OpenLayers.Util.getParameters=function(url){url=url||window.location.href;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;i<pairs.length;++i){var keyValue=pairs[i].split('=');if(keyValue[0]){var key=decodeURIComponent(keyValue[0]);var value=keyValue[1]||'';value=value.split(",");for(var j=0;j<value.length;j++){value[j]=decodeURIComponent(value[j]);}
+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=decodeURIComponent(keyValue[0]);var value=keyValue[1]||'';value=value.split(",");for(var j=0,jlen=value.length;j<jlen;j++){value[j]=decodeURIComponent(value[j]);}
 if(value.length==1){value=value[0];}
 parameters[key]=value;}}
 return parameters;};OpenLayers.Util.getArgs=function(url){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.Util.getParameters'}));return OpenLayers.Util.getParameters(url);};OpenLayers.Util.lastSeqID=0;OpenLayers.Util.createUniqueID=function(prefix){if(prefix==null){prefix="id_";}
 OpenLayers.Util.lastSeqID+=1;return prefix+OpenLayers.Util.lastSeqID;};OpenLayers.INCHES_PER_UNIT={'inches':1.0,'ft':12.0,'mi':63360.0,'m':39.3701,'km':39370.1,'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;OpenLayers.DOTS_PER_INCH=72;OpenLayers.Util.normalizeScale=function(scale){var normScale=(scale>1.0)?(1.0/scale):scale;return normScale;};OpenLayers.Util.getResolutionFromScale=function(scale,units){if(units==null){units="degrees";}
 var normScale=OpenLayers.Util.normalizeScale(scale);var resolution=1/(normScale*OpenLayers.INCHES_PER_UNIT[units]*OpenLayers.DOTS_PER_INCH);return resolution;};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;};OpenLayers.Util.safeStopPropagation=function(evt){OpenLayers.Event.stop(evt,true);};OpenLayers.Util.pagePosition=function(forElement){var valueT=0,valueL=0;var element=forElement;var child=forElement;while(element){if(element==document.body){if(child&&child.style&&OpenLayers.Element.getStyle(child,'position')=='absolute'){break;}}
+var scale=resolution*OpenLayers.INCHES_PER_UNIT[units]*OpenLayers.DOTS_PER_INCH;return scale;};OpenLayers.Util.safeStopPropagation=function(evt){OpenLayers.Event.stop(evt,true);};OpenLayers.Util.pagePosition=function(forElement){var valueT=0,valueL=0;var element=forElement;var child=forElement;while(element){if(element==document.body){if(OpenLayers.Element.getStyle(child,'position')=='absolute'){break;}}
 valueT+=element.offsetTop||0;valueL+=element.offsetLeft||0;child=element;try{element=element.offsetParent;}catch(e){OpenLayers.Console.error(OpenLayers.i18n("pagePositionFailed",{'elemId':element.id}));break;}}
 element=forElement;while(element){valueT-=element.scrollTop||0;valueL-=element.scrollLeft||0;element=element.parentNode;}
-return[valueL,valueT];};OpenLayers.Util.isEquivalentUrl=function(url1,url2,options){options=options||{};OpenLayers.Util.applyDefaults(options,{ignoreCase:true,ignorePort80:true,ignoreHash:true});var urlObj1=OpenLayers.Util.createUrlObject(url1,options);var urlObj2=OpenLayers.Util.createUrlObject(url2,options);for(var key in urlObj1){if(options.test){alert(key+"\n1:"+urlObj1[key]+"\n2:"+urlObj2[key]);}
+return[valueL,valueT];};OpenLayers.Util.isEquivalentUrl=function(url1,url2,options){options=options||{};OpenLayers.Util.applyDefaults(options,{ignoreCase:true,ignorePort80:true,ignoreHash:true});var urlObj1=OpenLayers.Util.createUrlObject(url1,options);var urlObj2=OpenLayers.Util.createUrlObject(url2,options);for(var key in urlObj1){if(options.test){OpenLayers.Console.userError(key+"\n1:"+urlObj1[key]+"\n2:"+urlObj2[key]);}
 var val1=urlObj1[key];var val2=urlObj2[key];switch(key){case"args":break;case"host":case"port":case"protocol":if((val1=="")||(val2=="")){break;}
 default:if((key!="args")&&(urlObj1[key]!=urlObj2[key])){return false;}
 break;}}
@@ -143,28 +134,36 @@
 if((urlObject.protocol=="file:")||(urlObject.protocol=="")){urlObject.host="localhost";}
 return urlObject;};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;};OpenLayers.Util.getBrowserName=function(){var browserName="";var ua=navigator.userAgent.toLowerCase();if(ua.indexOf("opera")!=-1){browserName="opera";}else if(ua.indexOf("msie")!=-1){browserName="msie";}else if(ua.indexOf("safari")!=-1){browserName="safari";}else if(ua.indexOf("mozilla")!=-1){if(ua.indexOf("firefox")!=-1){browserName="firefox";}else{browserName="mozilla";}}
-return browserName;};OpenLayers.Util.getRenderedDimensions=function(contentHTML,size){var w=h=null;var container=document.createElement("div");container.style.overflow="";container.style.position="absolute";container.style.left="-9999px";if(size){if(size.w){w=container.style.width=size.w;}else if(size.h){h=container.style.height=size.h;}}
+return browserName;};OpenLayers.Util.getRenderedDimensions=function(contentHTML,size,options){var w,h;var container=document.createElement("div");container.style.overflow="";container.style.position="absolute";container.style.left="-9999px";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";}}
+if(options&&options.displayClass){container.className=options.displayClass;}
 var content=document.createElement("div");content.innerHTML=contentHTML;container.appendChild(content);document.body.appendChild(container);if(!w){w=parseInt(content.scrollWidth);container.style.width=w+"px";}
 if(!h){h=parseInt(content.scrollHeight);}
 container.removeChild(content);document.body.removeChild(container);return new OpenLayers.Size(w,h);};OpenLayers.Util.getScrollbarWidth=function(){var scrollbarWidth=OpenLayers.Util._scrollbarWidth;if(scrollbarWidth==null){var scr=null;var inn=null;var wNoScroll=0;var wScroll=0;scr=document.createElement('div');scr.style.position='absolute';scr.style.top='-1000px';scr.style.left='-1000px';scr.style.width='100px';scr.style.height='50px';scr.style.overflow='hidden';inn=document.createElement('div');inn.style.width='100%';inn.style.height='200px';scr.appendChild(inn);document.body.appendChild(scr);wNoScroll=inn.offsetWidth;scr.style.overflow='scroll';wScroll=inn.offsetWidth;document.body.removeChild(document.body.lastChild);OpenLayers.Util._scrollbarWidth=(wNoScroll-wScroll);scrollbarWidth=OpenLayers.Util._scrollbarWidth;}
-return scrollbarWidth;};OpenLayers.ProxyHost="";OpenLayers.nullHandler=function(request){alert(OpenLayers.i18n("unhandledRequest",{'statusText':request.statusText}));};OpenLayers.loadURL=function(uri,params,caller,onComplete,onFailure){var success=(onComplete)?OpenLayers.Function.bind(onComplete,caller):OpenLayers.nullHandler;var failure=(onFailure)?OpenLayers.Function.bind(onFailure,caller):OpenLayers.nullHandler;var request=new OpenLayers.Ajax.Request(uri,{method:'get',parameters:params,onComplete:success,onFailure:failure});return request.transport;};OpenLayers.parseXMLString=function(text){var index=text.indexOf('<');if(index>0){text=text.substring(index);}
-var ajaxResponse=OpenLayers.Util.Try(function(){var xmldom=new ActiveXObject('Microsoft.XMLDOM');xmldom.loadXML(text);return xmldom;},function(){return new DOMParser().parseFromString(text,'text/xml');},function(){var req=new XMLHttpRequest();req.open("GET","data:"+"text/xml"+";charset=utf-8,"+encodeURIComponent(text),false);if(req.overrideMimeType){req.overrideMimeType("text/xml");}
-req.send(null);return req.responseXML;});return ajaxResponse;};OpenLayers.Ajax={emptyFunction:function(){},getTransport:function(){return OpenLayers.Util.Try(function(){return new XMLHttpRequest();},function(){return new ActiveXObject('Msxml2.XMLHTTP');},function(){return new ActiveXObject('Microsoft.XMLHTTP');})||false;},activeRequestCount:0};OpenLayers.Ajax.Responders={responders:[],register:function(responderToAdd){for(var i=0;i<this.responders.length;i++){if(responderToAdd==this.responders[i]){return;}}
-this.responders.push(responderToAdd);},unregister:function(responderToRemove){OpenLayers.Util.removeItem(this.reponders,responderToRemove);},dispatch:function(callback,request,transport){var responder;for(var i=0;i<this.responders.length;i++){responder=this.responders[i];if(responder[callback]&&typeof responder[callback]=='function'){try{responder[callback].apply(responder,[request,transport]);}catch(e){}}}}};OpenLayers.Ajax.Responders.register({onCreate:function(){OpenLayers.Ajax.activeRequestCount++;},onComplete:function(){OpenLayers.Ajax.activeRequestCount--;}});OpenLayers.Ajax.Base=OpenLayers.Class({initialize:function(options){this.options={method:'post',asynchronous:true,contentType:'application/xml',parameters:''};OpenLayers.Util.extend(this.options,options||{});this.options.method=this.options.method.toLowerCase();if(typeof this.options.parameters=='string'){this.options.parameters=OpenLayers.Util.getParameters(this.options.parameters);}}});OpenLayers.Ajax.Request=OpenLayers.Class(OpenLayers.Ajax.Base,{_complete:false,initialize:function(url,options){OpenLayers.Ajax.Base.prototype.initialize.apply(this,[options]);if(OpenLayers.ProxyHost&&OpenLayers.String.startsWith(url,"http")){url=OpenLayers.ProxyHost+encodeURIComponent(url);}
-this.transport=OpenLayers.Ajax.getTransport();this.request(url);},request:function(url){this.url=url;this.method=this.options.method;var params=OpenLayers.Util.extend({},this.options.parameters);if(this.method!='get'&&this.method!='post'){params['_method']=this.method;this.method='post';}
-this.parameters=params;if(params=OpenLayers.Util.getParameterString(params)){if(this.method=='get'){this.url+=((this.url.indexOf('?')>-1)?'&':'?')+params;}else if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)){params+='&_=';}}
-try{var response=new OpenLayers.Ajax.Response(this);if(this.options.onCreate){this.options.onCreate(response);}
-OpenLayers.Ajax.Responders.dispatch('onCreate',this,response);this.transport.open(this.method.toUpperCase(),this.url,this.options.asynchronous);if(this.options.asynchronous){window.setTimeout(OpenLayers.Function.bind(this.respondToReadyState,this,1),10);}
-this.transport.onreadystatechange=OpenLayers.Function.bind(this.onStateChange,this);this.setRequestHeaders();this.body=this.method=='post'?(this.options.postBody||params):null;this.transport.send(this.body);if(!this.options.asynchronous&&this.transport.overrideMimeType){this.onStateChange();}}catch(e){this.dispatchException(e);}},onStateChange:function(){var readyState=this.transport.readyState;if(readyState>1&&!((readyState==4)&&this._complete)){this.respondToReadyState(this.transport.readyState);}},setRequestHeaders:function(){var headers={'X-Requested-With':'XMLHttpRequest','Accept':'text/javascript, text/html, application/xml, text/xml, */*','OpenLayers':true};if(this.method=='post'){headers['Content-type']=this.options.contentType+
-(this.options.encoding?'; charset='+this.options.encoding:'');if(this.transport.overrideMimeType&&(navigator.userAgent.match(/Gecko\/(\d{4})/)||[0,2005])[1]<2005){headers['Connection']='close';}}
-if(typeof this.options.requestHeaders=='object'){var extras=this.options.requestHeaders;if(typeof extras.push=='function'){for(var i=0,length=extras.length;i<length;i+=2){headers[extras[i]]=extras[i+1];}}else{for(var i in extras){headers[i]=pair[i];}}}
-for(var name in headers){this.transport.setRequestHeader(name,headers[name]);}},success:function(){var status=this.getStatus();return!status||(status>=200&&status<300);},getStatus:function(){try{return this.transport.status||0;}catch(e){return 0;}},respondToReadyState:function(readyState){var state=OpenLayers.Ajax.Request.Events[readyState];var response=new OpenLayers.Ajax.Response(this);if(state=='Complete'){try{this._complete=true;(this.options['on'+response.status]||this.options['on'+(this.success()?'Success':'Failure')]||OpenLayers.Ajax.emptyFunction)(response);}catch(e){this.dispatchException(e);}
-var contentType=response.getHeader('Content-type');}
-try{(this.options['on'+state]||OpenLayers.Ajax.emptyFunction)(response);OpenLayers.Ajax.Responders.dispatch('on'+state,this,response);}catch(e){this.dispatchException(e);}
-if(state=='Complete'){this.transport.onreadystatechange=OpenLayers.Ajax.emptyFunction;}},getHeader:function(name){try{return this.transport.getResponseHeader(name);}catch(e){return null;}},dispatchException:function(exception){var handler=this.options.onException;if(handler){handler(this,exception);OpenLayers.Ajax.Responders.dispatch('onException',this,exception);}else{var listener=false;var responders=OpenLayers.Ajax.Responders.responders;for(var i=0;i<responders.length;i++){if(responders[i].onException){listener=true;break;}}
-if(listener){OpenLayers.Ajax.Responders.dispatch('onException',this,exception);}else{throw exception;}}}});OpenLayers.Ajax.Request.Events=['Uninitialized','Loading','Loaded','Interactive','Complete'];OpenLayers.Ajax.Response=OpenLayers.Class({status:0,statusText:'',initialize:function(request){this.request=request;var transport=this.transport=request.transport,readyState=this.readyState=transport.readyState;if((readyState>2&&!(!!(window.attachEvent&&!window.opera)))||readyState==4){this.status=this.getStatus();this.statusText=this.getStatusText();this.responseText=transport.responseText==null?'':String(transport.responseText);}
-if(readyState==4){var xml=transport.responseXML;this.responseXML=xml===undefined?null:xml;}},getStatus:OpenLayers.Ajax.Request.prototype.getStatus,getStatusText:function(){try{return this.transport.statusText||'';}catch(e){return'';}},getHeader:OpenLayers.Ajax.Request.prototype.getHeader,getResponseHeader:function(name){return this.transport.getResponseHeader(name);}});OpenLayers.Ajax.getElementsByTagNameNS=function(parentnode,nsuri,nsprefix,tagname){var elem=null;if(parentnode.getElementsByTagNameNS){elem=parentnode.getElementsByTagNameNS(nsuri,tagname);}else{elem=parentnode.getElementsByTagName(nsprefix+':'+tagname);}
-return elem;};OpenLayers.Ajax.serializeXMLToString=function(xmldom){var serializer=new XMLSerializer();var data=serializer.serializeToString(xmldom);return data;};OpenLayers.Console={log:function(){},debug:function(){},info:function(){},warn:function(){},error:function(){},assert:function(){},dir:function(){},dirxml:function(){},trace:function(){},group:function(){},groupEnd:function(){},time:function(){},timeEnd:function(){},profile:function(){},profileEnd:function(){},count:function(){},CLASS_NAME:"OpenLayers.Console"};(function(){if(window.console){var scripts=document.getElementsByTagName("script");for(var i=0;i<scripts.length;++i){if(scripts[i].src.indexOf("firebug.js")!=-1){OpenLayers.Util.extend(OpenLayers.Console,console);break;}}}})();OpenLayers.Size=OpenLayers.Class({w:0.0,h:0.0,initialize:function(w,h){this.w=parseFloat(w);this.h=parseFloat(h);},toString:function(){return("w="+this.w+",h="+this.h);},clone:function(){return new OpenLayers.Size(this.w,this.h);},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 scrollbarWidth;};OpenLayers.Console={log:function(){},debug:function(){},info:function(){},warn:function(){},error:function(){},userError:function(error){alert(error);},assert:function(){},dir:function(){},dirxml:function(){},trace:function(){},group:function(){},groupEnd:function(){},time:function(){},timeEnd:function(){},profile:function(){},profileEnd:function(){},count:function(){},CLASS_NAME:"OpenLayers.Console"};(function(){if(window.console){var scripts=document.getElementsByTagName("script");for(var i=0,len=scripts.length;i<len;++i){if(scripts[i].src.indexOf("firebug.js")!=-1){OpenLayers.Util.extend(OpenLayers.Console,console);break;}}}})();OpenLayers.String={startsWith:function(str,sub){return(str.indexOf(sub)==0);},contains:function(str,sub){return(str.indexOf(sub)!=-1);},trim:function(str){return str.replace(/^\s*(.*?)\s*$/,"$1");},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;},format:function(template,context,args){if(!context){context=window;}
+var tokens=template.split("${");var item,last,replacement;for(var i=1,len=tokens.length;i<len;i++){item=tokens[i];last=item.indexOf("}");if(last>0){replacement=context[item.substring(0,last)];if(typeof replacement=="function"){replacement=args?replacement.apply(null,args):replacement();}
+tokens[i]=replacement+item.substring(++last);}else{tokens[i]="${"+item;}}
+return tokens.join("");},numberRegEx:/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,isNumeric:function(value){return OpenLayers.String.numberRegEx.test(value);}};if(!String.prototype.startsWith){String.prototype.startsWith=function(sStart){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.startsWith'}));return OpenLayers.String.startsWith(this,sStart);};}
+if(!String.prototype.contains){String.prototype.contains=function(str){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.contains'}));return OpenLayers.String.contains(this,str);};}
+if(!String.prototype.trim){String.prototype.trim=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.trim'}));return OpenLayers.String.trim(this);};}
+if(!String.prototype.camelize){String.prototype.camelize=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.camelize'}));return OpenLayers.String.camelize(this);};}
+OpenLayers.Number={decimalSeparator:".",thousandsSeparator:",",limitSigDigs:function(num,sig){var fig=0;if(sig>0){fig=parseFloat(num.toPrecision(sig));}
+return fig;},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){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;}};if(!Number.prototype.limitSigDigs){Number.prototype.limitSigDigs=function(sig){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.limitSigDigs'}));return OpenLayers.Number.limitSigDigs(this,sig);};}
+OpenLayers.Function={bind:function(func,object){var args=Array.prototype.slice.apply(arguments,[2]);return function(){var newArgs=args.concat(Array.prototype.slice.apply(arguments,[0]));return func.apply(object,newArgs);};},bindAsEventListener:function(func,object){return function(event){return func.call(object,event||window.event);};}};if(!Function.prototype.bind){Function.prototype.bind=function(){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.bind'}));Array.prototype.unshift.apply(arguments,[this]);return OpenLayers.Function.bind.apply(null,arguments);};}
+if(!Function.prototype.bindAsEventListener){Function.prototype.bindAsEventListener=function(object){OpenLayers.Console.warn(OpenLayers.i18n("methodDeprecated",{'newMethod':'OpenLayers.String.bindAsEventListener'}));return OpenLayers.Function.bindAsEventListener(this,object);};}
+OpenLayers.Array={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.Class=function(){var Class=function(){if(arguments&&arguments[0]!=OpenLayers.Class.isPrototype){this.initialize.apply(this,arguments);}};var extended={};var parent;for(var i=0,len=arguments.length;i<len;++i){if(typeof arguments[i]=="function"){parent=arguments[i].prototype;}else{parent=arguments[i];}
+OpenLayers.Util.extend(extended,parent);}
+Class.prototype=extended;return Class;};OpenLayers.Class.isPrototype=function(){};OpenLayers.Class.create=function(){return function(){if(arguments&&arguments[0]!=OpenLayers.Class.isPrototype){this.initialize.apply(this,arguments);}};};OpenLayers.Class.inherit=function(){var superClass=arguments[0];var proto=new superClass(OpenLayers.Class.isPrototype);for(var i=1,len=arguments.length;i<len;i++){if(typeof arguments[i]=="function"){var mixin=arguments[i];arguments[i]=new mixin(OpenLayers.Class.isPrototype);}
+OpenLayers.Util.extend(proto,arguments[i]);}
+return proto;};OpenLayers.Size=OpenLayers.Class({w:0.0,h:0.0,initialize:function(w,h){this.w=parseFloat(w);this.h=parseFloat(h);},toString:function(){return("w="+this.w+",h="+this.h);},clone:function(){return new OpenLayers.Size(this.w,this.h);},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.Bounds=OpenLayers.Class({left:null,bottom:null,right:null,top:null,initialize:function(left,bottom,right,top){if(left!=null){this.left=parseFloat(left);}
 if(bottom!=null){this.bottom=parseFloat(bottom);}
 if(right!=null){this.right=parseFloat(right);}
@@ -174,7 +173,9 @@
 var mult=Math.pow(10,decimal);var bbox=Math.round(this.left*mult)/mult+","+
 Math.round(this.bottom*mult)/mult+","+
 Math.round(this.right*mult)/mult+","+
-Math.round(this.top*mult)/mult;return bbox;},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)])]);},getWidth:function(){return(this.right-this.left);},getHeight:function(){return(this.top-this.bottom);},getSize:function(){return new OpenLayers.Size(this.getWidth(),this.getHeight());},getCenterPixel:function(){return new OpenLayers.Pixel((this.left+this.right)/2,(this.bottom+this.top)/2);},getCenterLonLat:function(){return new OpenLayers.LonLat((this.left+this.right)/2,(this.bottom+this.top)/2);},add:function(x,y){if((x==null)||(y==null)){var msg=OpenLayers.i18n("boundsAddError");OpenLayers.Console.error(msg);return null;}
+Math.round(this.top*mult)/mult;return bbox;},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)])]);},getWidth:function(){return(this.right-this.left);},getHeight:function(){return(this.top-this.bottom);},getSize:function(){return new OpenLayers.Size(this.getWidth(),this.getHeight());},getCenterPixel:function(){return new OpenLayers.Pixel((this.left+this.right)/2,(this.bottom+this.top)/2);},getCenterLonLat:function(){return new OpenLayers.LonLat((this.left+this.right)/2,(this.bottom+this.top)/2);},scale:function(ratio,origin){if(origin==null){origin=this.getCenterLonLat();}
+var bounds=[];var origx,origy;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);},add:function(x,y){if((x==null)||(y==null)){var msg=OpenLayers.i18n("boundsAddError");OpenLayers.Console.error(msg);return null;}
 return new OpenLayers.Bounds(this.left+x,this.bottom+y,this.right+x,this.top+y);},extend:function(object){var bounds=null;if(object){switch(object.CLASS_NAME){case"OpenLayers.LonLat":bounds=new OpenLayers.Bounds(object.lon,object.lat,object.lon,object.lat);break;case"OpenLayers.Geometry.Point":bounds=new OpenLayers.Bounds(object.x,object.y,object.x,object.y);break;case"OpenLayers.Bounds":bounds=object;break;}
 if(bounds){if((this.left==null)||(bounds.left<this.left)){this.left=bounds.left;}
 if((this.bottom==null)||(bounds.bottom<this.bottom)){this.bottom=bounds.bottom;}
@@ -187,9 +188,12 @@
 var inLeft;var inTop;var inRight;var inBottom;if(inclusive){inLeft=(bounds.left>=this.left)&&(bounds.left<=this.right);inTop=(bounds.top>=this.bottom)&&(bounds.top<=this.top);inRight=(bounds.right>=this.left)&&(bounds.right<=this.right);inBottom=(bounds.bottom>=this.bottom)&&(bounds.bottom<=this.top);}else{inLeft=(bounds.left>this.left)&&(bounds.left<this.right);inTop=(bounds.top>this.bottom)&&(bounds.top<this.top);inRight=(bounds.right>this.left)&&(bounds.right<this.right);inBottom=(bounds.bottom>this.bottom)&&(bounds.bottom<this.top);}
 return(partial)?(inTop||inBottom)&&(inLeft||inRight):(inTop&&inLeft&&inBottom&&inRight);},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;},transform:function(source,dest){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;},wrapDateLine:function(maxExtent,options){options=options||{};var leftTolerance=options.leftTolerance||0;var rightTolerance=options.rightTolerance||0;var newBounds=this.clone();if(maxExtent){while(newBounds.left<maxExtent.left&&(newBounds.right-rightTolerance)<=maxExtent.left){newBounds=newBounds.add(maxExtent.getWidth(),0);}
 while((newBounds.left+leftTolerance)>=maxExtent.right&&newBounds.right>maxExtent.right){newBounds=newBounds.add(-maxExtent.getWidth(),0);}}
-return newBounds;},CLASS_NAME:"OpenLayers.Bounds"});OpenLayers.Bounds.fromString=function(str){var bounds=str.split(",");return OpenLayers.Bounds.fromArray(bounds);};OpenLayers.Bounds.fromArray=function(bbox){return new OpenLayers.Bounds(parseFloat(bbox[0]),parseFloat(bbox[1]),parseFloat(bbox[2]),parseFloat(bbox[3]));};OpenLayers.Bounds.fromSize=function(size){return new OpenLayers.Bounds(0,size.h,size.w,0);};OpenLayers.Bounds.oppositeQuadrant=function(quadrant){var opp="";opp+=(quadrant.charAt(0)=='t')?'b':'t';opp+=(quadrant.charAt(1)=='l')?'r':'l';return opp;};OpenLayers.Element={visible:function(element){return OpenLayers.Util.getElement(element).style.display!='none';},toggle:function(){for(var i=0;i<arguments.length;i++){var element=OpenLayers.Util.getElement(arguments[i]);var display=OpenLayers.Element.visible(element)?'hide':'show';OpenLayers.Element[display](element);}},hide:function(){for(var i=0;i<arguments.length;i++){var element=OpenLayers.Util.getElement(arguments[i]);element.style.display='none';}},show:function(){for(var i=0;i<arguments.length;i++){var element=OpenLayers.Util.getElement(arguments[i]);element.style.display='';}},remove:function(element){element=OpenLayers.Util.getElement(element);element.parentNode.removeChild(element);},getHeight:function(element){element=OpenLayers.Util.getElement(element);return element.offsetHeight;},getDimensions:function(element){element=OpenLayers.Util.getElement(element);if(OpenLayers.Element.getStyle(element,'display')!='none'){return{width:element.offsetWidth,height:element.offsetHeight};}
-var els=element.style;var originalVisibility=els.visibility;var originalPosition=els.position;els.visibility='hidden';els.position='absolute';els.display='';var originalWidth=element.clientWidth;var originalHeight=element.clientHeight;els.display='none';els.position=originalPosition;els.visibility=originalVisibility;return{width:originalWidth,height:originalHeight};},getStyle:function(element,style){element=OpenLayers.Util.getElement(element);var 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 newBounds;},CLASS_NAME:"OpenLayers.Bounds"});OpenLayers.Bounds.fromString=function(str){var bounds=str.split(",");return OpenLayers.Bounds.fromArray(bounds);};OpenLayers.Bounds.fromArray=function(bbox){return new OpenLayers.Bounds(parseFloat(bbox[0]),parseFloat(bbox[1]),parseFloat(bbox[2]),parseFloat(bbox[3]));};OpenLayers.Bounds.fromSize=function(size){return new OpenLayers.Bounds(0,size.h,size.w,0);};OpenLayers.Bounds.oppositeQuadrant=function(quadrant){var opp="";opp+=(quadrant.charAt(0)=='t')?'b':'t';opp+=(quadrant.charAt(1)=='l')?'r':'l';return opp;};OpenLayers.Element={visible:function(element){return OpenLayers.Util.getElement(element).style.display!='none';},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)?'hide':'show';OpenLayers.Element[display](element);}},hide:function(){for(var i=0,len=arguments.length;i<len;i++){var element=OpenLayers.Util.getElement(arguments[i]);element.style.display='none';}},show:function(){for(var i=0,len=arguments.length;i<len;i++){var element=OpenLayers.Util.getElement(arguments[i]);element.style.display='';}},remove:function(element){element=OpenLayers.Util.getElement(element);element.parentNode.removeChild(element);},getHeight:function(element){element=OpenLayers.Util.getElement(element);return element.offsetHeight;},getDimensions:function(element){element=OpenLayers.Util.getElement(element);if(OpenLayers.Element.getStyle(element,'display')!='none'){return{width:element.offsetWidth,height:element.offsetHeight};}
+var els=element.style;var originalVisibility=els.visibility;var originalPosition=els.position;els.visibility='hidden';els.position='absolute';els.display='';var originalWidth=element.clientWidth;var originalHeight=element.clientHeight;els.display='none';els.position=originalPosition;els.visibility=originalVisibility;return{width:originalWidth,height:originalHeight};},hasClass:function(element,name){var names=element.className;return(!!names&&new RegExp("(^|\\s)"+name+"(\\s|$)").test(names));},addClass:function(element,name){if(!OpenLayers.Element.hasClass(element,name)){element.className+=(element.className?" ":"")+name;}
+return 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;},toggleClass:function(element,name){if(OpenLayers.Element.hasClass(element,name)){OpenLayers.Element.removeClass(element,name);}else{OpenLayers.Element.addClass(element,name);}
+return element;},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.LonLat=OpenLayers.Class({lon:0.0,lat:0.0,initialize:function(lon,lat){this.lon=parseFloat(lon);this.lat=parseFloat(lat);},toString:function(){return("lon="+this.lon+",lat="+this.lat);},toShortString:function(){return(this.lon+", "+this.lat);},clone:function(){return new OpenLayers.LonLat(this.lon,this.lat);},add:function(lon,lat){if((lon==null)||(lat==null)){var msg=OpenLayers.i18n("lonlatAddError");OpenLayers.Console.error(msg);return null;}
 return new OpenLayers.LonLat(this.lon+lon,this.lat+lat);},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;},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;},wrapDateLine:function(maxExtent){var newLonLat=this.clone();if(maxExtent){while(newLonLat.lon<maxExtent.left){newLonLat.lon+=maxExtent.getWidth();}
@@ -198,7 +202,7 @@
 return equals;},add:function(x,y){if((x==null)||(y==null)){var msg=OpenLayers.i18n("pixelAddError");OpenLayers.Console.error(msg);return null;}
 return new OpenLayers.Pixel(this.x+x,this.y+y);},offset:function(px){var newPx=this.clone();if(px){newPx=this.add(px.x,px.y);}
 return newPx;},CLASS_NAME:"OpenLayers.Pixel"});OpenLayers.Control=OpenLayers.Class({id:null,map:null,div:null,type:null,allowSelection:false,displayClass:"",title:"",active:null,handler:null,eventListeners:null,events:null,EVENT_TYPES:["activate","deactivate"],initialize:function(options){this.displayClass=this.CLASS_NAME.replace("OpenLayers.","ol").replace(/\./g,"");OpenLayers.Util.extend(this,options);this.events=new OpenLayers.Events(this,null,this.EVENT_TYPES);if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}
-this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){if(this.events){if(this.eventListeners){this.events.un(this.eventListeners);}
+if(this.id==null){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");}},destroy:function(){if(this.events){if(this.eventListeners){this.events.un(this.eventListeners);}
 this.events.destroy();this.events=null;}
 this.eventListeners=null;if(this.handler){this.handler.destroy();this.handler=null;}
 if(this.handlers){for(var key in this.handlers){if(this.handlers.hasOwnProperty(key)&&typeof this.handlers[key].destroy=="function"){this.handlers[key].destroy();}}
@@ -221,7 +225,54 @@
 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;},translate:function(key,context){var dictionary=OpenLayers.Lang[OpenLayers.Lang.getCode()];var message=dictionary[key];if(!message){message=key;}
 if(context){message=OpenLayers.String.format(message,context);}
-return message;}};OpenLayers.i18n=OpenLayers.Lang.translate;OpenLayers.Tween=OpenLayers.Class({INTERVAL:10,easing:null,begin:null,finish:null,duration:null,callbacks:null,time:null,interval:null,playing:false,initialize:function(easing){this.easing=(easing)?easing:OpenLayers.Easing.Expo.easeOut;},start:function(begin,finish,duration,options){this.playing=true;this.begin=begin;this.finish=finish;this.duration=duration;this.callbacks=options.callbacks;this.time=0;if(this.interval){window.clearInterval(this.interval);this.interval=null;}
+return message;}};OpenLayers.i18n=OpenLayers.Lang.translate;OpenLayers.Popup=OpenLayers.Class({events:null,id:"",lonlat:null,div:null,contentSize:null,size:null,contentHTML:null,backgroundColor:"",opacity:"",border:"",contentDiv:null,groupDiv:null,closeDiv:null,autoSize:false,minSize:null,maxSize:null,displayClass:"olPopup",contentDisplayClass:"olPopupContent",padding:0,fixPadding:function(){if(typeof this.padding=="number"){this.padding=new OpenLayers.Bounds(this.padding,this.padding,this.padding,this.padding);}},panMapIfOutOfView:false,map:null,initialize:function(id,lonlat,contentSize,contentHTML,closeBox,closeBoxCallback){if(id==null){id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");}
+this.id=id;this.lonlat=lonlat;this.contentSize=(contentSize!=null)?contentSize:new OpenLayers.Size(OpenLayers.Popup.WIDTH,OpenLayers.Popup.HEIGHT);if(contentHTML!=null){this.contentHTML=contentHTML;}
+this.backgroundColor=OpenLayers.Popup.COLOR;this.opacity=OpenLayers.Popup.OPACITY;this.border=OpenLayers.Popup.BORDER;this.div=OpenLayers.Util.createDiv(this.id,null,null,null,null,null,"hidden");this.div.className=this.displayClass;var groupDivId=this.id+"_GroupDiv";this.groupDiv=OpenLayers.Util.createDiv(groupDivId,null,null,null,"relative",null,"hidden");var id=this.div.id+"_contentDiv";this.contentDiv=OpenLayers.Util.createDiv(id,null,this.contentSize.clone(),null,"relative");this.contentDiv.className=this.contentDisplayClass;this.groupDiv.appendChild(this.contentDiv);this.div.appendChild(this.groupDiv);if(closeBox){this.addCloseBox(closeBoxCallback);}
+this.registerEvents();},destroy:function(){this.id=null;this.lonlat=null;this.size=null;this.contentHTML=null;this.backgroundColor=null;this.opacity=null;this.border=null;this.events.destroy();this.events=null;if(this.closeDiv){OpenLayers.Event.stopObservingElement(this.closeDiv);this.groupDiv.removeChild(this.closeDiv);}
+this.closeDiv=null;this.div.removeChild(this.groupDiv);this.groupDiv=null;if(this.map!=null){this.map.removePopup(this);}
+this.map=null;this.div=null;this.autoSize=null;this.minSize=null;this.maxSize=null;this.padding=null;this.panMapIfOutOfView=null;},draw:function(px){if(px==null){if((this.lonlat!=null)&&(this.map!=null)){px=this.map.getLayerPxFromLonLat(this.lonlat);}}
+if(OpenLayers.Util.getBrowserName()=='firefox'){this.map.events.register("movestart",this,function(){var style=document.defaultView.getComputedStyle(this.contentDiv,null);var currentOverflow=style.getPropertyValue("overflow");if(currentOverflow!="hidden"){this.contentDiv._oldOverflow=currentOverflow;this.contentDiv.style.overflow="hidden";}});this.map.events.register("moveend",this,function(){var oldOverflow=this.contentDiv._oldOverflow;if(oldOverflow){this.contentDiv.style.overflow=oldOverflow;this.contentDiv._oldOverflow=null;}});}
+this.moveTo(px);if(!this.autoSize&&!this.size){this.setSize(this.contentSize);}
+this.setBackgroundColor();this.setOpacity();this.setBorder();this.setContentHTML();if(this.panMapIfOutOfView){this.panIntoView();}
+return this.div;},updatePosition:function(){if((this.lonlat)&&(this.map)){var px=this.map.getLayerPxFromLonLat(this.lonlat);if(px){this.moveTo(px);}}},moveTo:function(px){if((px!=null)&&(this.div!=null)){this.div.style.left=px.x+"px";this.div.style.top=px.y+"px";}},visible:function(){return OpenLayers.Element.visible(this.div);},toggle:function(){if(this.visible()){this.hide();}else{this.show();}},show:function(){OpenLayers.Element.show(this.div);if(this.panMapIfOutOfView){this.panIntoView();}},hide:function(){OpenLayers.Element.hide(this.div);},setSize:function(contentSize){this.size=contentSize.clone();var contentDivPadding=this.getContentDivPadding();var wPadding=contentDivPadding.left+contentDivPadding.right;var hPadding=contentDivPadding.top+contentDivPadding.bottom;this.fixPadding();wPadding+=this.padding.left+this.padding.right;hPadding+=this.padding.top+this.padding.bottom;if(this.closeDiv){var closeDivWidth=parseInt(this.closeDiv.style.width);wPadding+=closeDivWidth+contentDivPadding.right;}
+this.size.w+=wPadding;this.size.h+=hPadding;if(OpenLayers.Util.getBrowserName()=="msie"){this.contentSize.w+=contentDivPadding.left+contentDivPadding.right;this.contentSize.h+=contentDivPadding.bottom+contentDivPadding.top;}
+if(this.div!=null){this.div.style.width=this.size.w+"px";this.div.style.height=this.size.h+"px";}
+if(this.contentDiv!=null){this.contentDiv.style.width=contentSize.w+"px";this.contentDiv.style.height=contentSize.h+"px";}},updateSize:function(){var preparedHTML="<div class='"+this.contentDisplayClass+"'>"+
+this.contentDiv.innerHTML+"<div>";var realSize=OpenLayers.Util.getRenderedDimensions(preparedHTML,null,{displayClass:this.displayClass});var safeSize=this.getSafeContentSize(realSize);var newSize=null;if(safeSize.equals(realSize)){newSize=realSize;}else{var fixedSize=new OpenLayers.Size();fixedSize.w=(safeSize.w<realSize.w)?safeSize.w:null;fixedSize.h=(safeSize.h<realSize.h)?safeSize.h:null;if(fixedSize.w&&fixedSize.h){newSize=safeSize;}else{var clippedSize=OpenLayers.Util.getRenderedDimensions(preparedHTML,fixedSize,{displayClass:this.contentDisplayClass});var currentOverflow=OpenLayers.Element.getStyle(this.contentDiv,"overflow");if((currentOverflow!="hidden")&&(clippedSize.equals(safeSize))){var scrollBar=OpenLayers.Util.getScrollbarWidth();if(fixedSize.w){clippedSize.h+=scrollBar;}else{clippedSize.w+=scrollBar;}}
+newSize=this.getSafeContentSize(clippedSize);}}
+this.setSize(newSize);},setBackgroundColor:function(color){if(color!=undefined){this.backgroundColor=color;}
+if(this.div!=null){this.div.style.backgroundColor=this.backgroundColor;}},setOpacity:function(opacity){if(opacity!=undefined){this.opacity=opacity;}
+if(this.div!=null){this.div.style.opacity=this.opacity;this.div.style.filter='alpha(opacity='+this.opacity*100+')';}},setBorder:function(border){if(border!=undefined){this.border=border;}
+if(this.div!=null){this.div.style.border=this.border;}},setContentHTML:function(contentHTML){if(contentHTML!=null){this.contentHTML=contentHTML;}
+if((this.contentDiv!=null)&&(this.contentHTML!=null)&&(this.contentHTML!=this.contentDiv.innerHTML)){this.contentDiv.innerHTML=this.contentHTML;if(this.autoSize){this.registerImageListeners();this.updateSize();}}},registerImageListeners:function(){var onImgLoad=function(){this.popup.updateSize();if(this.popup.visible()&&this.popup.panMapIfOutOfView){this.popup.panIntoView();}
+OpenLayers.Event.stopObserving(this.img,"load",this.img._onImageLoad);};var images=this.contentDiv.getElementsByTagName("img");for(var i=0,len=images.length;i<len;i++){var img=images[i];if(img.width==0||img.height==0){var context={'popup':this,'img':img};img._onImgLoad=OpenLayers.Function.bind(onImgLoad,context);OpenLayers.Event.observe(img,'load',img._onImgLoad);}}},getSafeContentSize:function(size){var safeContentSize=size.clone();var contentDivPadding=this.getContentDivPadding();var wPadding=contentDivPadding.left+contentDivPadding.right;var hPadding=contentDivPadding.top+contentDivPadding.bottom;this.fixPadding();wPadding+=this.padding.left+this.padding.right;hPadding+=this.padding.top+this.padding.bottom;if(this.closeDiv){var closeDivWidth=parseInt(this.closeDiv.style.width);wPadding+=closeDivWidth+contentDivPadding.right;}
+if(this.minSize){safeContentSize.w=Math.max(safeContentSize.w,(this.minSize.w-wPadding));safeContentSize.h=Math.max(safeContentSize.h,(this.minSize.h-hPadding));}
+if(this.maxSize){safeContentSize.w=Math.min(safeContentSize.w,(this.maxSize.w-wPadding));safeContentSize.h=Math.min(safeContentSize.h,(this.maxSize.h-hPadding));}
+if(this.map&&this.map.size){var maxY=this.map.size.h-
+this.map.paddingForPopups.top-
+this.map.paddingForPopups.bottom-
+hPadding;var maxX=this.map.size.w-
+this.map.paddingForPopups.left-
+this.map.paddingForPopups.right-
+wPadding;safeContentSize.w=Math.min(safeContentSize.w,maxX);safeContentSize.h=Math.min(safeContentSize.h,maxY);}
+return safeContentSize;},getContentDivPadding:function(){var contentDivPadding=this._contentDivPadding;if(!contentDivPadding){this.div.style.display="none";document.body.appendChild(this.div);contentDivPadding=new OpenLayers.Bounds(OpenLayers.Element.getStyle(this.contentDiv,"padding-left"),OpenLayers.Element.getStyle(this.contentDiv,"padding-bottom"),OpenLayers.Element.getStyle(this.contentDiv,"padding-right"),OpenLayers.Element.getStyle(this.contentDiv,"padding-top"));this._contentDivPadding=contentDivPadding;document.body.removeChild(this.div);this.div.style.display="";}
+return contentDivPadding;},addCloseBox:function(callback){this.closeDiv=OpenLayers.Util.createDiv(this.id+"_close",null,new OpenLayers.Size(17,17));this.closeDiv.className="olPopupCloseBox";var contentDivPadding=this.getContentDivPadding();this.closeDiv.style.right=contentDivPadding.right+"px";this.closeDiv.style.top=contentDivPadding.top+"px";this.groupDiv.appendChild(this.closeDiv);var closePopup=callback||function(e){this.hide();OpenLayers.Event.stop(e);};OpenLayers.Event.observe(this.closeDiv,"click",OpenLayers.Function.bindAsEventListener(closePopup,this));},panIntoView:function(){var mapSize=this.map.getSize();var origTL=this.map.getViewPortPxFromLayerPx(new OpenLayers.Pixel(parseInt(this.div.style.left),parseInt(this.div.style.top)));var newTL=origTL.clone();if(origTL.x<this.map.paddingForPopups.left){newTL.x=this.map.paddingForPopups.left;}else
+if((origTL.x+this.size.w)>(mapSize.w-this.map.paddingForPopups.right)){newTL.x=mapSize.w-this.map.paddingForPopups.right-this.size.w;}
+if(origTL.y<this.map.paddingForPopups.top){newTL.y=this.map.paddingForPopups.top;}else
+if((origTL.y+this.size.h)>(mapSize.h-this.map.paddingForPopups.bottom)){newTL.y=mapSize.h-this.map.paddingForPopups.bottom-this.size.h;}
+var dx=origTL.x-newTL.x;var dy=origTL.y-newTL.y;this.map.pan(dx,dy);},registerEvents:function(){this.events=new OpenLayers.Events(this,this.div,null,true);this.events.on({"mousedown":this.onmousedown,"mousemove":this.onmousemove,"mouseup":this.onmouseup,"click":this.onclick,"mouseout":this.onmouseout,"dblclick":this.ondblclick,scope:this});},onmousedown:function(evt){this.mousedown=true;OpenLayers.Event.stop(evt,true);},onmousemove:function(evt){if(this.mousedown){OpenLayers.Event.stop(evt,true);}},onmouseup:function(evt){if(this.mousedown){this.mousedown=false;OpenLayers.Event.stop(evt,true);}},onclick:function(evt){OpenLayers.Event.stop(evt,true);},onmouseout:function(evt){this.mousedown=false;},ondblclick:function(evt){OpenLayers.Event.stop(evt,true);},CLASS_NAME:"OpenLayers.Popup"});OpenLayers.Popup.WIDTH=200;OpenLayers.Popup.HEIGHT=200;OpenLayers.Popup.COLOR="white";OpenLayers.Popup.OPACITY=1;OpenLayers.Popup.BORDER="0px";OpenLayers.Renderer=OpenLayers.Class({container:null,extent:null,locked:false,size:null,resolution:null,map:null,initialize:function(containerID,options){this.container=OpenLayers.Util.getElement(containerID);},destroy:function(){this.container=null;this.extent=null;this.size=null;this.resolution=null;this.map=null;},supported:function(){return false;},setExtent:function(extent,resolutionChanged){this.extent=extent.clone();if(resolutionChanged){this.resolution=null;}},setSize:function(size){this.size=size.clone();this.resolution=null;},getResolution:function(){this.resolution=this.resolution||this.map.getResolution();return this.resolution;},drawFeature:function(feature,style){if(style==null){style=feature.style;}
+if(feature.geometry){if(!feature.geometry.getBounds().intersectsBounds(this.extent)){style={display:"none"};}
+return this.drawGeometry(feature.geometry,style,feature.id);}},drawGeometry:function(geometry,style,featureId){},clear:function(){},getFeatureIdFromEvent:function(evt){},eraseFeatures:function(features){if(!(features instanceof Array)){features=[features];}
+for(var i=0,len=features.length;i<len;++i){this.eraseGeometry(features[i].geometry);}},eraseGeometry:function(geometry){},CLASS_NAME:"OpenLayers.Renderer"});OpenLayers.Request={DEFAULT_CONFIG:{method:"GET",url:window.location.href,async:true,user:undefined,password:undefined,params:null,proxy:OpenLayers.ProxyHost,headers:{},data:null,callback:function(){},success:null,failure:null,scope:null},issue:function(config){var defaultConfig=OpenLayers.Util.extend(this.DEFAULT_CONFIG,{proxy:OpenLayers.ProxyHost});config=OpenLayers.Util.applyDefaults(config,defaultConfig);var request=new OpenLayers.Request.XMLHttpRequest();var url=config.url;if(config.params){url+="?"+OpenLayers.Util.getParameterString(config.params);}
+if(config.proxy&&(url.indexOf("http")==0)){url=config.proxy+encodeURIComponent(url);}
+request.open(config.method,url,config.async,config.user,config.password);for(var header in config.headers){request.setRequestHeader(header,config.headers[header]);}
+var complete=(config.scope)?OpenLayers.Function.bind(config.callback,config.scope):config.callback;var success;if(config.success){success=(config.scope)?OpenLayers.Function.bind(config.success,config.scope):config.success;}
+var failure;if(config.failure){failure=(config.scope)?OpenLayers.Function.bind(config.failure,config.scope):config.failure;}
+request.onreadystatechange=function(){if(request.readyState==OpenLayers.Request.XMLHttpRequest.DONE){complete(request);if(success&&(!request.status||(request.status>=200&&request.status<300))){success(request);}
+if(failure&&(request.status&&(request.status<200||request.status>=300))){failure(request);}}}
+request.send(config.data);return request;},GET:function(config){config=OpenLayers.Util.extend(config,{method:"GET"});return OpenLayers.Request.issue(config);},POST:function(config){config=OpenLayers.Util.extend(config,{method:"POST"});config.headers=config.headers?config.headers:{};if(!("CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(config.headers))){config.headers["Content-Type"]="application/xml";}
+return OpenLayers.Request.issue(config);},PUT:function(config){config=OpenLayers.Util.extend(config,{method:"PUT"});config.headers=config.headers?config.headers:{};if(!("CONTENT-TYPE"in OpenLayers.Util.upperCaseObject(config.headers))){config.headers["Content-Type"]="application/xml";}
+return OpenLayers.Request.issue(config);},DELETE:function(config){config=OpenLayers.Util.extend(config,{method:"DELETE"});return OpenLayers.Request.issue(config);},HEAD:function(config){config=OpenLayers.Util.extend(config,{method:"HEAD"});return OpenLayers.Request.issue(config);},OPTIONS:function(config){config=OpenLayers.Util.extend(config,{method:"OPTIONS"});return OpenLayers.Request.issue(config);}};OpenLayers.Tween=OpenLayers.Class({INTERVAL:10,easing:null,begin:null,finish:null,duration:null,callbacks:null,time:null,interval:null,playing:false,initialize:function(easing){this.easing=(easing)?easing:OpenLayers.Easing.Expo.easeOut;},start:function(begin,finish,duration,options){this.playing=true;this.begin=begin;this.finish=finish;this.duration=duration;this.callbacks=options.callbacks;this.time=0;if(this.interval){window.clearInterval(this.interval);this.interval=null;}
 if(this.callbacks&&this.callbacks.start){this.callbacks.start.call(this,this.begin);}
 this.interval=window.setInterval(OpenLayers.Function.bind(this.play,this),this.INTERVAL);},stop:function(){if(!this.playing){return;}
 if(this.callbacks&&this.callbacks.done){this.callbacks.done.call(this,this.finish);}
@@ -229,31 +280,15 @@
 var c=f-b;value[i]=this.easing.apply(this,[this.time,b,c,this.duration]);}
 this.time++;if(this.callbacks&&this.callbacks.eachStep){this.callbacks.eachStep.call(this,value);}
 if(this.time>this.duration){if(this.callbacks&&this.callbacks.done){this.callbacks.done.call(this,this.finish);this.playing=false;}
-window.clearInterval(this.interval);this.interval=null;}},CLASS_NAME:"OpenLayers.Tween"});OpenLayers.Easing={CLASS_NAME:"OpenLayers.Easing"};OpenLayers.Easing.Linear={easeIn:function(t,b,c,d){return c*t/d+b;},easeOut:function(t,b,c,d){return c*t/d+b;},easeInOut:function(t,b,c,d){return c*t/d+b;},CLASS_NAME:"OpenLayers.Easing.Linear"};OpenLayers.Easing.Expo={easeIn:function(t,b,c,d){return(t==0)?b:c*Math.pow(2,10*(t/d-1))+b;},easeOut:function(t,b,c,d){return(t==d)?b+c:c*(-Math.pow(2,-10*t/d)+1)+b;},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"};OpenLayers.Easing.Quad={easeIn:function(t,b,c,d){return c*(t/=d)*t+b;},easeOut:function(t,b,c,d){return-c*(t/=d)*(t-2)+b;},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.Control.ArgParser=OpenLayers.Class(OpenLayers.Control,{center:null,zoom:null,layers:null,displayProjection:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},setMap:function(map){OpenLayers.Control.prototype.setMap.apply(this,arguments);for(var i=0;i<this.map.controls.length;i++){var control=this.map.controls[i];if((control!=this)&&(control.CLASS_NAME=="OpenLayers.Control.ArgParser")){if(control.displayProjection!=this.displayProjection){this.displayProjection=control.displayProjection;}
+window.clearInterval(this.interval);this.interval=null;}},CLASS_NAME:"OpenLayers.Tween"});OpenLayers.Easing={CLASS_NAME:"OpenLayers.Easing"};OpenLayers.Easing.Linear={easeIn:function(t,b,c,d){return c*t/d+b;},easeOut:function(t,b,c,d){return c*t/d+b;},easeInOut:function(t,b,c,d){return c*t/d+b;},CLASS_NAME:"OpenLayers.Easing.Linear"};OpenLayers.Easing.Expo={easeIn:function(t,b,c,d){return(t==0)?b:c*Math.pow(2,10*(t/d-1))+b;},easeOut:function(t,b,c,d){return(t==d)?b+c:c*(-Math.pow(2,-10*t/d)+1)+b;},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"};OpenLayers.Easing.Quad={easeIn:function(t,b,c,d){return c*(t/=d)*t+b;},easeOut:function(t,b,c,d){return-c*(t/=d)*(t-2)+b;},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.Control.ArgParser=OpenLayers.Class(OpenLayers.Control,{center:null,zoom:null,layers:null,displayProjection:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},setMap:function(map){OpenLayers.Control.prototype.setMap.apply(this,arguments);for(var i=0,len=this.map.controls.length;i<len;i++){var control=this.map.controls[i];if((control!=this)&&(control.CLASS_NAME=="OpenLayers.Control.ArgParser")){if(control.displayProjection!=this.displayProjection){this.displayProjection=control.displayProjection;}
 break;}}
 if(i==this.map.controls.length){var args=OpenLayers.Util.getParameters();if(args.layers){this.layers=args.layers;this.map.events.register('addlayer',this,this.configureLayers);this.configureLayers();}
 if(args.lat&&args.lon){this.center=new OpenLayers.LonLat(parseFloat(args.lon),parseFloat(args.lat));if(args.zoom){this.zoom=parseInt(args.zoom);}
 this.map.events.register('changebaselayer',this,this.setCenter);this.setCenter();}}},setCenter:function(){if(this.map.baseLayer){this.map.events.unregister('changebaselayer',this,this.setCenter);if(this.displayProjection){this.center.transform(this.displayProjection,this.map.getProjectionObject());}
-this.map.setCenter(this.center,this.zoom);}},configureLayers:function(){if(this.layers.length==this.map.layers.length){this.map.events.unregister('addlayer',this,this.configureLayers);for(var i=0;i<this.layers.length;i++){var layer=this.map.layers[i];var c=this.layers.charAt(i);if(c=="B"){this.map.setBaseLayer(layer);}else if((c=="T")||(c=="F")){layer.setVisibility(c=="T");}}}},CLASS_NAME:"OpenLayers.Control.ArgParser"});OpenLayers.Control.MousePosition=OpenLayers.Class(OpenLayers.Control,{element:null,prefix:'',separator:', ',suffix:'',numdigits:5,granularity:10,lastXy:null,displayProjection:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){if(this.map){this.map.events.unregister('mousemove',this,this.redraw);}
-OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.element){this.div.left="";this.div.top="";this.element=this.div;}
-this.redraw();return this.div;},redraw:function(evt){var lonLat;if(evt==null){lonLat=new OpenLayers.LonLat(0,0);}else{if(this.lastXy==null||Math.abs(evt.xy.x-this.lastXy.x)>this.granularity||Math.abs(evt.xy.y-this.lastXy.y)>this.granularity)
-{this.lastXy=evt.xy;return;}
-lonLat=this.map.getLonLatFromPixel(evt.xy);if(!lonLat){return;}
-if(this.displayProjection){lonLat.transform(this.map.getProjectionObject(),this.displayProjection);}
-this.lastXy=evt.xy;}
-var newHtml=this.formatOutput(lonLat);if(newHtml!=this.element.innerHTML){this.element.innerHTML=newHtml;}},formatOutput:function(lonLat){var digits=parseInt(this.numdigits);var newHtml=this.prefix+
-lonLat.lon.toFixed(digits)+
-this.separator+
-lonLat.lat.toFixed(digits)+
-this.suffix;return newHtml;},setMap:function(){OpenLayers.Control.prototype.setMap.apply(this,arguments);this.map.events.register('mousemove',this,this.redraw);},CLASS_NAME:"OpenLayers.Control.MousePosition"});OpenLayers.Control.PanZoom=OpenLayers.Class(OpenLayers.Control,{slideFactor:50,buttons:null,position:null,initialize:function(options){this.position=new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,OpenLayers.Control.PanZoom.Y);OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this,arguments);while(this.buttons.length){var btn=this.buttons.shift();btn.map=null;OpenLayers.Event.stopObservingElement(btn);}
-this.buttons=null;this.position=null;},draw:function(px){OpenLayers.Control.prototype.draw.apply(this,arguments);px=this.position;this.buttons=[];var sz=new OpenLayers.Size(18,18);var centered=new OpenLayers.Pixel(px.x+sz.w/2,px.y);this._addButton("panup","north-mini.png",centered,sz);px.y=centered.y+sz.h;this._addButton("panleft","west-mini.png",px,sz);this._addButton("panright","east-mini.png",px.add(sz.w,0),sz);this._addButton("pandown","south-mini.png",centered.add(0,sz.h*2),sz);this._addButton("zoomin","zoom-plus-mini.png",centered.add(0,sz.h*3+5),sz);this._addButton("zoomworld","zoom-world-mini.png",centered.add(0,sz.h*4+5),sz);this._addButton("zoomout","zoom-minus-mini.png",centered.add(0,sz.h*5+5),sz);return this.div;},_addButton:function(id,img,xy,sz){var imgLocation=OpenLayers.Util.getImagesLocation()+img;var btn=OpenLayers.Util.createAlphaImageDiv("OpenLayers_Control_PanZoom_"+id,xy,sz,imgLocation,"absolute");this.div.appendChild(btn);OpenLayers.Event.observe(btn,"mousedown",OpenLayers.Function.bindAsEventListener(this.buttonDown,btn));OpenLayers.Event.observe(btn,"dblclick",OpenLayers.Function.bindAsEventListener(this.doubleClick,btn));OpenLayers.Event.observe(btn,"click",OpenLayers.Function.bindAsEventListener(this.doubleClick,btn));btn.action=id;btn.map=this.map;btn.slideFactor=this.slideFactor;this.buttons.push(btn);return btn;},doubleClick:function(evt){OpenLayers.Event.stop(evt);return false;},buttonDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
+this.map.setCenter(this.center,this.zoom);}},configureLayers:function(){if(this.layers.length==this.map.layers.length){this.map.events.unregister('addlayer',this,this.configureLayers);for(var i=0,len=this.layers.length;i<len;i++){var layer=this.map.layers[i];var c=this.layers.charAt(i);if(c=="B"){this.map.setBaseLayer(layer);}else if((c=="T")||(c=="F")){layer.setVisibility(c=="T");}}}},CLASS_NAME:"OpenLayers.Control.ArgParser"});OpenLayers.Control.PanZoom=OpenLayers.Class(OpenLayers.Control,{slideFactor:50,buttons:null,position:null,initialize:function(options){this.position=new OpenLayers.Pixel(OpenLayers.Control.PanZoom.X,OpenLayers.Control.PanZoom.Y);OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){OpenLayers.Control.prototype.destroy.apply(this,arguments);while(this.buttons.length){var btn=this.buttons.shift();btn.map=null;OpenLayers.Event.stopObservingElement(btn);}
+this.buttons=null;this.position=null;},draw:function(px){OpenLayers.Control.prototype.draw.apply(this,arguments);px=this.position;this.buttons=[];var sz=new OpenLayers.Size(18,18);var centered=new OpenLayers.Pixel(px.x+sz.w/2,px.y);this._addButton("panup","north-mini.png",centered,sz);px.y=centered.y+sz.h;this._addButton("panleft","west-mini.png",px,sz);this._addButton("panright","east-mini.png",px.add(sz.w,0),sz);this._addButton("pandown","south-mini.png",centered.add(0,sz.h*2),sz);this._addButton("zoomin","zoom-plus-mini.png",centered.add(0,sz.h*3+5),sz);this._addButton("zoomworld","zoom-world-mini.png",centered.add(0,sz.h*4+5),sz);this._addButton("zoomout","zoom-minus-mini.png",centered.add(0,sz.h*5+5),sz);return this.div;},_addButton:function(id,img,xy,sz){var imgLocation=OpenLayers.Util.getImagesLocation()+img;var btn=OpenLayers.Util.createAlphaImageDiv(this.id+"_"+id,xy,sz,imgLocation,"absolute");this.div.appendChild(btn);OpenLayers.Event.observe(btn,"mousedown",OpenLayers.Function.bindAsEventListener(this.buttonDown,btn));OpenLayers.Event.observe(btn,"dblclick",OpenLayers.Function.bindAsEventListener(this.doubleClick,btn));OpenLayers.Event.observe(btn,"click",OpenLayers.Function.bindAsEventListener(this.doubleClick,btn));btn.action=id;btn.map=this.map;btn.slideFactor=this.slideFactor;this.buttons.push(btn);return btn;},doubleClick:function(evt){OpenLayers.Event.stop(evt);return false;},buttonDown:function(evt){if(!OpenLayers.Event.isLeftClick(evt)){return;}
 switch(this.action){case"panup":this.map.pan(0,-this.slideFactor);break;case"pandown":this.map.pan(0,this.slideFactor);break;case"panleft":this.map.pan(-this.slideFactor,0);break;case"panright":this.map.pan(this.slideFactor,0);break;case"zoomin":this.map.zoomIn();break;case"zoomout":this.map.zoomOut();break;case"zoomworld":this.map.zoomToMaxExtent();break;}
-OpenLayers.Event.stop(evt);},CLASS_NAME:"OpenLayers.Control.PanZoom"});OpenLayers.Control.PanZoom.X=4;OpenLayers.Control.PanZoom.Y=4;OpenLayers.Control.ScaleLine=OpenLayers.Class(OpenLayers.Control,{maxWidth:100,topOutUnits:"km",topInUnits:"m",bottomOutUnits:"mi",bottomInUnits:"ft",eTop:null,eBottom:null,initialize:function(options){OpenLayers.Control.prototype.initialize.apply(this,[options]);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!this.eTop){this.div.style.display="block";this.div.style.position="absolute";this.eTop=document.createElement("div");this.eTop.className=this.displayClass+"Top";var theLen=this.topInUnits.length;this.div.appendChild(this.eTop);if((this.topOutUnits=="")||(this.topInUnits=="")){this.eTop.style.visibility="hidden";}else{this.eTop.style.visibility="visible";}
-this.eBottom=document.createElement("div");this.eBottom.className=this.displayClass+"Bottom";this.div.appendChild(this.eBottom);if((this.bottomOutUnits=="")||(this.bottomInUnits=="")){this.eBottom.style.visibility="hidden";}else{this.eBottom.style.visibility="visible";}}
-this.map.events.register('moveend',this,this.update);this.update();return this.div;},getBarLen:function(maxLen){var digits=parseInt(Math.log(maxLen)/Math.log(10));var pow10=Math.pow(10,digits);var firstChar=parseInt(maxLen/pow10);var barLen;if(firstChar>5){barLen=5;}else if(firstChar>2){barLen=2;}else{barLen=1;}
-return barLen*pow10;},update:function(){var res=this.map.getResolution();if(!res){return;}
-var curMapUnits=this.map.units;var inches=OpenLayers.INCHES_PER_UNIT;var maxSizeData=this.maxWidth*res*inches[curMapUnits];var topUnits;var bottomUnits;if(maxSizeData>100000){topUnits=this.topOutUnits;bottomUnits=this.bottomOutUnits;}else{topUnits=this.topInUnits;bottomUnits=this.bottomInUnits;}
-var topMax=maxSizeData/inches[topUnits];var bottomMax=maxSizeData/inches[bottomUnits];var topRounded=this.getBarLen(topMax);var bottomRounded=this.getBarLen(bottomMax);topMax=topRounded/inches[curMapUnits]*inches[topUnits];bottomMax=bottomRounded/inches[curMapUnits]*inches[bottomUnits];var topPx=topMax/res;var bottomPx=bottomMax/res;this.eTop.style.width=Math.round(topPx)+"px";this.eBottom.style.width=Math.round(bottomPx)+"px";this.eTop.innerHTML=topRounded+" "+topUnits;this.eBottom.innerHTML=bottomRounded+" "+bottomUnits;},CLASS_NAME:"OpenLayers.Control.ScaleLine"});OpenLayers.Event={observers:false,KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,element:function(event){return event.target||event.srcElement;},isLeftClick:function(event){return(((event.which)&&(event.which==1))||((event.button)&&(event.button==1)));},stop:function(event,allowDefault){if(!allowDefault){if(event.preventDefault){event.preventDefault();}else{event.returnValue=false;}}
+OpenLayers.Event.stop(evt);},CLASS_NAME:"OpenLayers.Control.PanZoom"});OpenLayers.Control.PanZoom.X=4;OpenLayers.Control.PanZoom.Y=4;OpenLayers.Event={observers:false,KEY_BACKSPACE:8,KEY_TAB:9,KEY_RETURN:13,KEY_ESC:27,KEY_LEFT:37,KEY_UP:38,KEY_RIGHT:39,KEY_DOWN:40,KEY_DELETE:46,element:function(event){return event.target||event.srcElement;},isLeftClick:function(event){return(((event.which)&&(event.which==1))||((event.button)&&(event.button==1)));},isRightClick:function(event){return(((event.which)&&(event.which==3))||((event.button)&&(event.button==2)));},stop:function(event,allowDefault){if(!allowDefault){if(event.preventDefault){event.preventDefault();}else{event.returnValue=false;}}
 if(event.stopPropagation){event.stopPropagation();}else{event.cancelBubble=true;}},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;},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(!this.observers){this.observers={};}
@@ -267,39 +302,84 @@
 if(foundEntry){if(element.removeEventListener){element.removeEventListener(name,observer,useCapture);}else if(element&&element.detachEvent){element.detachEvent('on'+name,observer);}}
 return foundEntry;},unloadCache:function(){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"};OpenLayers.Event.observe(window,'unload',OpenLayers.Event.unloadCache,false);if(window.Event){OpenLayers.Util.applyDefaults(window.Event,OpenLayers.Event);}else{var Event=OpenLayers.Event;}
-OpenLayers.Events=OpenLayers.Class({BROWSER_EVENTS:["mouseover","mouseout","mousedown","mouseup","mousemove","click","dblclick","resize","focus","blur"],listeners:null,object:null,element:null,eventTypes:null,eventHandler:null,fallThrough:null,initialize:function(object,element,eventTypes,fallThrough){this.object=object;this.element=element;this.eventTypes=eventTypes;this.fallThrough=fallThrough;this.listeners={};this.eventHandler=OpenLayers.Function.bindAsEventListener(this.handleBrowserEvent,this);if(this.eventTypes!=null){for(var i=0;i<this.eventTypes.length;i++){this.addEventType(this.eventTypes[i]);}}
+OpenLayers.Events=OpenLayers.Class({BROWSER_EVENTS:["mouseover","mouseout","mousedown","mouseup","mousemove","click","dblclick","rightclick","dblrightclick","resize","focus","blur"],listeners:null,object:null,element:null,eventTypes:null,eventHandler:null,fallThrough:null,includeXY:false,initialize:function(object,element,eventTypes,fallThrough,options){OpenLayers.Util.extend(this,options);this.object=object;this.element=element;this.fallThrough=fallThrough;this.listeners={};this.eventHandler=OpenLayers.Function.bindAsEventListener(this.handleBrowserEvent,this);this.eventTypes=[];if(eventTypes!=null){for(var i=0,len=eventTypes.length;i<len;i++){this.addEventType(eventTypes[i]);}}
 if(this.element!=null){this.attachToElement(element);}},destroy:function(){if(this.element){OpenLayers.Event.stopObservingElement(this.element);}
-this.element=null;this.listeners=null;this.object=null;this.eventTypes=null;this.fallThrough=null;this.eventHandler=null;},addEventType:function(eventName){if(!this.listeners[eventName]){this.listeners[eventName]=[];}},attachToElement:function(element){for(var i=0;i<this.BROWSER_EVENTS.length;i++){var eventType=this.BROWSER_EVENTS[i];this.addEventType(eventType);OpenLayers.Event.observe(element,eventType,this.eventHandler);}
-OpenLayers.Event.observe(element,"dragstart",OpenLayers.Event.stop);},on:function(object){for(var type in object){if(type!="scope"){this.register(type,object.scope,object[type]);}}},register:function(type,obj,func){if(func!=null&&((this.eventTypes&&OpenLayers.Util.indexOf(this.eventTypes,type)!=-1)||OpenLayers.Util.indexOf(this.BROWSER_EVENTS,type)!=-1)){if(obj==null){obj=this.object;}
-var listeners=this.listeners[type];if(listeners!=null){listeners.push({obj:obj,func:func});}}},registerPriority:function(type,obj,func){if(func!=null){if(obj==null){obj=this.object;}
+this.element=null;this.listeners=null;this.object=null;this.eventTypes=null;this.fallThrough=null;this.eventHandler=null;},addEventType:function(eventName){if(!this.listeners[eventName]){this.eventTypes.push(eventName);this.listeners[eventName]=[];}},attachToElement:function(element){for(var i=0,len=this.BROWSER_EVENTS.length;i<len;i++){var eventType=this.BROWSER_EVENTS[i];this.addEventType(eventType);OpenLayers.Event.observe(element,eventType,this.eventHandler);}
+OpenLayers.Event.observe(element,"dragstart",OpenLayers.Event.stop);},on:function(object){for(var type in object){if(type!="scope"){this.register(type,object.scope,object[type]);}}},register:function(type,obj,func){if((func!=null)&&(OpenLayers.Util.indexOf(this.eventTypes,type)!=-1)){if(obj==null){obj=this.object;}
+var listeners=this.listeners[type];listeners.push({obj:obj,func:func});}},registerPriority:function(type,obj,func){if(func!=null){if(obj==null){obj=this.object;}
 var listeners=this.listeners[type];if(listeners!=null){listeners.unshift({obj:obj,func:func});}}},un:function(object){for(var type in object){if(type!="scope"){this.unregister(type,object.scope,object[type]);}}},unregister:function(type,obj,func){if(obj==null){obj=this.object;}
-var listeners=this.listeners[type];if(listeners!=null){for(var i=0;i<listeners.length;i++){if(listeners[i].obj==obj&&listeners[i].func==func){listeners.splice(i,1);break;}}}},remove:function(type){if(this.listeners[type]!=null){this.listeners[type]=[];}},triggerEvent:function(type,evt){if(evt==null){evt={};}
+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;}}}},remove:function(type){if(this.listeners[type]!=null){this.listeners[type]=[];}},triggerEvent:function(type,evt){if(evt==null){evt={};}
 evt.object=this.object;evt.element=this.element;if(!evt.type){evt.type=type;}
-var listeners=(this.listeners[type])?this.listeners[type].slice():null;if((listeners!=null)&&(listeners.length>0)){var continueChain;for(var i=0;i<listeners.length;i++){var callback=listeners[i];continueChain=callback.func.apply(callback.obj,[evt]);if((continueChain!=undefined)&&(continueChain==false)){break;}}
+var listeners=(this.listeners[type])?this.listeners[type].slice():null;if((listeners!=null)&&(listeners.length>0)){var continueChain;for(var i=0,len=listeners.length;i<len;i++){var callback=listeners[i];continueChain=callback.func.apply(callback.obj,[evt]);if((continueChain!=undefined)&&(continueChain==false)){break;}}
 if(!this.fallThrough){OpenLayers.Event.stop(evt,true);}}
-return continueChain;},handleBrowserEvent:function(evt){evt.xy=this.getMousePosition(evt);this.triggerEvent(evt.type,evt);},getMousePosition:function(evt){if(!this.element.offsets){this.element.offsets=OpenLayers.Util.pagePosition(this.element);this.element.offsets[0]+=(document.documentElement.scrollLeft||document.body.scrollLeft);this.element.offsets[1]+=(document.documentElement.scrollTop||document.body.scrollTop);}
-return new OpenLayers.Pixel((evt.clientX+(document.documentElement.scrollLeft||document.body.scrollLeft))-this.element.offsets[0]
--(document.documentElement.clientLeft||0),(evt.clientY+(document.documentElement.scrollTop||document.body.scrollTop))-this.element.offsets[1]
--(document.documentElement.clientTop||0));},CLASS_NAME:"OpenLayers.Events"});OpenLayers.Lang.en={'unhandledRequest':"Unhandled request return ${statusText}",'permalink':"Permalink",'overlays':"Overlays",'baseLayer':"Base Layer",'sameProjection':"The overview map only works when it is in the same projection as the main map",'readNotImplemented':"Read not implemented.",'writeNotImplemented':"Write not implemented.",'noFID':"Can't update a feature for which there is no FID.",'errorLoadingGML':"Error in loading GML file ${url}",'browserNotSupported':"Your browser does not support vector rendering. Currently supported renderers are:\n${renderers}",'componentShouldBe':"addFeatures : component should be an ${geomType}",'getFeatureError':"getFeatureFromEvent called on layer with no renderer. This usually means you "+"destroyed a layer, but not some handler which is associated with it.",'minZoomLevelError':"The minZoomLevel property is only intended for use "+"with the FixedZoomLevels-descendent layers. That this "+"wfs layer checks for minZoomLevel is a relic of the"+"past. We cannot, however, remove it without possibly "+"breaking OL based applications that may depend on it."+" Therefore we are deprecating it -- the minZoomLevel "+"check below will be removed at 3.0. Please instead "+"use min/max resolution setting as described here: "+"http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"WFS Transaction: SUCCESS ${response}",'commitFailed':"WFS Transaction: FAILED ${response}",'googleWarning':"The Google Layer was unable to load correctly.<br><br>"+"To get rid of this message, select a new BaseLayer "+"in the layer switcher in the upper-right corner.<br><br>"+"Most likely, this is because the Google Maps library "+"script was either not included, or does not contain the "+"correct API key for your site.<br><br>"+"Developers: For help getting this working correctly, "+"<a href='http://trac.openlayers.org/wiki/Google' "+"target='_blank'>click here</a>",'getLayerWarning':"The ${layerType} Layer was unable to load correctly.<br><br>"+"To get rid of this message, select a new BaseLayer "+"in the layer switcher in the upper-right corner.<br><br>"+"Most likely, this is because the ${layerLib} library "+"script was either not correctly included.<br><br>"+"Developers: For help getting this working correctly, "+"<a href='http://trac.openlayers.org/wiki/${layerLib}' "+"target='_blank'>click here</a>",'scale':"Scale = 1 : ${scaleDenom}",'layerAlreadyAdded':"You tried to add the layer: ${layerName} to the map, but it has already been added",'reprojectDeprecated':"You are using the 'reproject' option "+"on the ${layerName} layer. This option is deprecated: "+"its use was designed to support displaying data over commercial "+"basemaps, but that functionality should now be achieved by using "+"Spherical Mercator support. More information is available from "+"http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"This method has been deprecated and will be removed in 3.0. "+"Please use ${newMethod} instead.",'boundsAddError':"You must pass both x and y values to the add function.",'lonlatAddError':"You must pass both lon and lat values to the add function.",'pixelAddError':"You must pass both x and y values to the add function.",'unsupportedGeometryType':"Unsupported geometry type: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition failed: element with id ${elemId} may be misplaced.",'end':''};OpenLayers.Projection=OpenLayers.Class({proj:null,projCode:null,initialize:function(projCode,options){OpenLayers.Util.extend(this,options);this.projCode=projCode;if(window.Proj4js){this.proj=new Proj4js.Proj(projCode);}},getCode:function(){return this.proj?this.proj.srsCode:this.projCode;},getUnits:function(){return this.proj?this.proj.units:null;},toString:function(){return this.getCode();},equals:function(projection){if(projection&&projection.getCode){return this.getCode()==projection.getCode();}else{return false;}},destroy:function(){delete this.proj;delete this.projCode;},CLASS_NAME:"OpenLayers.Projection"});OpenLayers.Projection.transforms={};OpenLayers.Projection.addTransform=function(from,to,method){if(!OpenLayers.Projection.transforms[from]){OpenLayers.Projection.transforms[from]={};}
+return continueChain;},handleBrowserEvent:function(evt){if(this.includeXY){evt.xy=this.getMousePosition(evt);}
+this.triggerEvent(evt.type,evt);},clearMouseCache:function(){this.element.scrolls=null;this.element.lefttop=null;this.element.offsets=null;},getMousePosition:function(evt){if(!this.includeXY){this.clearMouseCache();}else if(!this.element.hasScrollEvent){OpenLayers.Event.observe(window,'scroll',OpenLayers.Function.bind(this.clearMouseCache,this));this.element.hasScrollEvent=true;}
+if(!this.element.scrolls){this.element.scrolls=[];this.element.scrolls[0]=(document.documentElement.scrollLeft||document.body.scrollLeft);this.element.scrolls[1]=(document.documentElement.scrollTop||document.body.scrollTop);}
+if(!this.element.lefttop){this.element.lefttop=[];this.element.lefttop[0]=(document.documentElement.clientLeft||0);this.element.lefttop[1]=(document.documentElement.clientTop||0);}
+if(!this.element.offsets){this.element.offsets=OpenLayers.Util.pagePosition(this.element);this.element.offsets[0]+=this.element.scrolls[0];this.element.offsets[1]+=this.element.scrolls[1];}
+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]);},CLASS_NAME:"OpenLayers.Events"});OpenLayers.Format=OpenLayers.Class({externalProjection:null,internalProjection:null,initialize:function(options){OpenLayers.Util.extend(this,options);},read:function(data){OpenLayers.Console.userError(OpenLayers.i18n("readNotImplemented"));},write:function(object){OpenLayers.Console.userError(OpenLayers.i18n("writeNotImplemented"));},CLASS_NAME:"OpenLayers.Format"});OpenLayers.Lang.en={'unhandledRequest':"Unhandled request return ${statusText}",'permalink':"Permalink",'overlays':"Overlays",'baseLayer':"Base Layer",'sameProjection':"The overview map only works when it is in the same projection as the main map",'readNotImplemented':"Read not implemented.",'writeNotImplemented':"Write not implemented.",'noFID':"Can't update a feature for which there is no FID.",'errorLoadingGML':"Error in loading GML file ${url}",'browserNotSupported':"Your browser does not support vector rendering. Currently supported renderers are:\n${renderers}",'componentShouldBe':"addFeatures : component should be an ${geomType}",'getFeatureError':"getFeatureFromEvent called on layer with no renderer. This usually means you "+"destroyed a layer, but not some handler which is associated with it.",'minZoomLevelError':"The minZoomLevel property is only intended for use "+"with the FixedZoomLevels-descendent layers. That this "+"wfs layer checks for minZoomLevel is a relic of the"+"past. We cannot, however, remove it without possibly "+"breaking OL based applications that may depend on it."+" Therefore we are deprecating it -- the minZoomLevel "+"check below will be removed at 3.0. Please instead "+"use min/max resolution setting as described here: "+"http://trac.openlayers.org/wiki/SettingZoomLevels",'commitSuccess':"WFS Transaction: SUCCESS ${response}",'commitFailed':"WFS Transaction: FAILED ${response}",'googleWarning':"The Google Layer was unable to load correctly.<br><br>"+"To get rid of this message, select a new BaseLayer "+"in the layer switcher in the upper-right corner.<br><br>"+"Most likely, this is because the Google Maps library "+"script was either not included, or does not contain the "+"correct API key for your site.<br><br>"+"Developers: For help getting this working correctly, "+"<a href='http://trac.openlayers.org/wiki/Google' "+"target='_blank'>click here</a>",'getLayerWarning':"The ${layerType} Layer was unable to load correctly.<br><br>"+"To get rid of this message, select a new BaseLayer "+"in the layer switcher in the upper-right corner.<br><br>"+"Most likely, this is because the ${layerLib} library "+"script was not correctly included.<br><br>"+"Developers: For help getting this working correctly, "+"<a href='http://trac.openlayers.org/wiki/${layerLib}' "+"target='_blank'>click here</a>",'scale':"Scale = 1 : ${scaleDenom}",'layerAlreadyAdded':"You tried to add the layer: ${layerName} to the map, but it has already been added",'reprojectDeprecated':"You are using the 'reproject' option "+"on the ${layerName} layer. This option is deprecated: "+"its use was designed to support displaying data over commercial "+"basemaps, but that functionality should now be achieved by using "+"Spherical Mercator support. More information is available from "+"http://trac.openlayers.org/wiki/SphericalMercator.",'methodDeprecated':"This method has been deprecated and will be removed in 3.0. "+"Please use ${newMethod} instead.",'boundsAddError':"You must pass both x and y values to the add function.",'lonlatAddError':"You must pass both lon and lat values to the add function.",'pixelAddError':"You must pass both x and y values to the add function.",'unsupportedGeometryType':"Unsupported geometry type: ${geomType}",'pagePositionFailed':"OpenLayers.Util.pagePosition failed: element with id ${elemId} may be misplaced.",'end':''};OpenLayers.Popup.Anchored=OpenLayers.Class(OpenLayers.Popup,{relativePosition:null,anchor:null,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){var newArguments=[id,lonlat,contentSize,contentHTML,closeBox,closeBoxCallback];OpenLayers.Popup.prototype.initialize.apply(this,newArguments);this.anchor=(anchor!=null)?anchor:{size:new OpenLayers.Size(0,0),offset:new OpenLayers.Pixel(0,0)};},destroy:function(){this.anchor=null;this.relativePosition=null;OpenLayers.Popup.prototype.destroy.apply(this,arguments);},show:function(){this.updatePosition();OpenLayers.Popup.prototype.show.apply(this,arguments);},moveTo:function(px){var oldRelativePosition=this.relativePosition;this.relativePosition=this.calculateRelativePosition(px);var newPx=this.calculateNewPx(px);var newArguments=new Array(newPx);OpenLayers.Popup.prototype.moveTo.apply(this,newArguments);if(this.relativePosition!=oldRelativePosition){this.updateRelativePosition();}},setSize:function(contentSize){OpenLayers.Popup.prototype.setSize.apply(this,arguments);if((this.lonlat)&&(this.map)){var px=this.map.getLayerPxFromLonLat(this.lonlat);this.moveTo(px);}},calculateRelativePosition:function(px){var lonlat=this.map.getLonLatFromLayerPx(px);var extent=this.map.getExtent();var quadrant=extent.determineQuadrant(lonlat);return OpenLayers.Bounds.oppositeQuadrant(quadrant);},updateRelativePosition:function(){},calculateNewPx:function(px){var newPx=px.offset(this.anchor.offset);var size=this.size||this.contentSize;var top=(this.relativePosition.charAt(0)=='t');newPx.y+=(top)?-size.h:this.anchor.size.h;var left=(this.relativePosition.charAt(1)=='l');newPx.x+=(left)?-size.w:this.anchor.size.w;return newPx;},CLASS_NAME:"OpenLayers.Popup.Anchored"});OpenLayers.Projection=OpenLayers.Class({proj:null,projCode:null,initialize:function(projCode,options){OpenLayers.Util.extend(this,options);this.projCode=projCode;if(window.Proj4js){this.proj=new Proj4js.Proj(projCode);}},getCode:function(){return this.proj?this.proj.srsCode:this.projCode;},getUnits:function(){return this.proj?this.proj.units:null;},toString:function(){return this.getCode();},equals:function(projection){if(projection&&projection.getCode){return this.getCode()==projection.getCode();}else{return false;}},destroy:function(){delete this.proj;delete this.projCode;},CLASS_NAME:"OpenLayers.Projection"});OpenLayers.Projection.transforms={};OpenLayers.Projection.addTransform=function(from,to,method){if(!OpenLayers.Projection.transforms[from]){OpenLayers.Projection.transforms[from]={};}
 OpenLayers.Projection.transforms[from][to]=method;};OpenLayers.Projection.transform=function(point,source,dest){if(source.proj&&dest.proj){point=Proj4js.transform(source.proj,dest.proj,point);}else if(source&&dest&&OpenLayers.Projection.transforms[source.getCode()]&&OpenLayers.Projection.transforms[source.getCode()][dest.getCode()]){OpenLayers.Projection.transforms[source.getCode()][dest.getCode()](point);}
-return point;};OpenLayers.Tile=OpenLayers.Class({EVENT_TYPES:["loadstart","loadend","reload","unload"],events:null,id:null,layer:null,url:null,bounds:null,size:null,position:null,isLoading:false,isBackBuffer:false,lastRatio:1,isFirstDraw:true,backBufferTile:null,initialize:function(layer,position,bounds,url,size){this.layer=layer;this.position=position.clone();this.bounds=bounds.clone();this.url=url;this.size=size.clone();this.id=OpenLayers.Util.createUniqueID("Tile_");this.events=new OpenLayers.Events(this,null,this.EVENT_TYPES);},unload:function(){if(this.isLoading){this.isLoading=false;this.events.triggerEvent("unload");}},destroy:function(){if(OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1){this.layer.events.unregister("loadend",this,this.resetBackBuffer);this.events.unregister('loadend',this,this.resetBackBuffer);}else{this.events.unregister('loadend',this,this.showTile);}
-this.layer=null;this.bounds=null;this.size=null;this.position=null;this.events.destroy();this.events=null;if(this.backBufferTile){this.backBufferTile.destroy();this.backBufferTile=null;}},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile(this.layer,this.position,this.bounds,this.url,this.size);}
-OpenLayers.Util.applyDefaults(obj,this);return obj;},draw:function(){var maxExtent=this.layer.maxExtent;var withinMaxExtent=(maxExtent&&this.bounds.intersectsBounds(maxExtent,false));var drawTile=(withinMaxExtent||this.layer.displayOutsideMaxExtent);if(OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1){if(drawTile){if(!this.backBufferTile){this.backBufferTile=this.clone();this.backBufferTile.hide();this.backBufferTile.isBackBuffer=true;this.events.register('loadend',this,this.resetBackBuffer);this.layer.events.register("loadend",this,this.resetBackBuffer);}
-this.startTransition();}else{if(this.backBufferTile){this.backBufferTile.clear();}}}else{if(drawTile&&this.isFirstDraw){this.events.register('loadend',this,this.showTile);this.isFirstDraw=false;}}
-this.shouldDraw=drawTile;this.clear();return drawTile;},moveTo:function(bounds,position,redraw){if(redraw==null){redraw=true;}
+return point;};(function(){var oXMLHttpRequest=window.XMLHttpRequest;var bGecko=!!window.controllers,bIE=window.document.all&&!window.opera;function cXMLHttpRequest(){this._object=oXMLHttpRequest?new oXMLHttpRequest:new window.ActiveXObject('Microsoft.XMLHTTP');};if(bGecko&&oXMLHttpRequest.wrapped)
+cXMLHttpRequest.wrapped=oXMLHttpRequest.wrapped;cXMLHttpRequest.UNSENT=0;cXMLHttpRequest.OPENED=1;cXMLHttpRequest.HEADERS_RECEIVED=2;cXMLHttpRequest.LOADING=3;cXMLHttpRequest.DONE=4;cXMLHttpRequest.prototype.readyState=cXMLHttpRequest.UNSENT;cXMLHttpRequest.prototype.responseText="";cXMLHttpRequest.prototype.responseXML=null;cXMLHttpRequest.prototype.status=0;cXMLHttpRequest.prototype.statusText="";cXMLHttpRequest.prototype.onreadystatechange=null;cXMLHttpRequest.onreadystatechange=null;cXMLHttpRequest.onopen=null;cXMLHttpRequest.onsend=null;cXMLHttpRequest.onabort=null;cXMLHttpRequest.prototype.open=function(sMethod,sUrl,bAsync,sUser,sPassword){this._async=bAsync;var oRequest=this,nState=this.readyState;if(bIE){var fOnUnload=function(){if(oRequest._object.readyState!=cXMLHttpRequest.DONE)
+fCleanTransport(oRequest);};if(bAsync)
+window.attachEvent("onunload",fOnUnload);}
+this._object.onreadystatechange=function(){if(bGecko&&!bAsync)
+return;oRequest.readyState=oRequest._object.readyState;fSynchronizeValues(oRequest);if(oRequest._aborted){oRequest.readyState=cXMLHttpRequest.UNSENT;return;}
+if(oRequest.readyState==cXMLHttpRequest.DONE){fCleanTransport(oRequest);if(bIE&&bAsync)
+window.detachEvent("onunload",fOnUnload);}
+if(nState!=oRequest.readyState)
+fReadyStateChange(oRequest);nState=oRequest.readyState;};if(cXMLHttpRequest.onopen)
+cXMLHttpRequest.onopen.apply(this,arguments);this._object.open(sMethod,sUrl,bAsync,sUser,sPassword);if(!bAsync&&bGecko){this.readyState=cXMLHttpRequest.OPENED;fReadyStateChange(this);}};cXMLHttpRequest.prototype.send=function(vData){if(cXMLHttpRequest.onsend)
+cXMLHttpRequest.onsend.apply(this,arguments);if(vData&&vData.nodeType){vData=window.XMLSerializer?new window.XMLSerializer().serializeToString(vData):vData.xml;if(!this._headers["Content-Type"])
+this._object.setRequestHeader("Content-Type","application/xml");}
+this._object.send(vData);if(bGecko&&!this._async){this.readyState=cXMLHttpRequest.OPENED;fSynchronizeValues(this);while(this.readyState<cXMLHttpRequest.DONE){this.readyState++;fReadyStateChange(this);if(this._aborted)
+return;}}};cXMLHttpRequest.prototype.abort=function(){if(cXMLHttpRequest.onabort)
+cXMLHttpRequest.onabort.apply(this,arguments);if(this.readyState>cXMLHttpRequest.UNSENT)
+this._aborted=true;this._object.abort();fCleanTransport(this);};cXMLHttpRequest.prototype.getAllResponseHeaders=function(){return this._object.getAllResponseHeaders();};cXMLHttpRequest.prototype.getResponseHeader=function(sName){return this._object.getResponseHeader(sName);};cXMLHttpRequest.prototype.setRequestHeader=function(sName,sValue){if(!this._headers)
+this._headers={};this._headers[sName]=sValue;return this._object.setRequestHeader(sName,sValue);};cXMLHttpRequest.prototype.toString=function(){return'['+"object"+' '+"XMLHttpRequest"+']';};cXMLHttpRequest.toString=function(){return'['+"XMLHttpRequest"+']';};function fReadyStateChange(oRequest){if(oRequest.onreadystatechange)
+oRequest.onreadystatechange.apply(oRequest);if(cXMLHttpRequest.onreadystatechange)
+cXMLHttpRequest.onreadystatechange.apply(oRequest);};function fGetDocument(oRequest){var oDocument=oRequest.responseXML;if(bIE&&oDocument&&!oDocument.documentElement&&oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)){oDocument=new ActiveXObject('Microsoft.XMLDOM');oDocument.loadXML(oRequest.responseText);}
+if(oDocument)
+if((bIE&&oDocument.parseError!=0)||(oDocument.documentElement&&oDocument.documentElement.tagName=="parsererror"))
+return null;return oDocument;};function fSynchronizeValues(oRequest){try{oRequest.responseText=oRequest._object.responseText;}catch(e){}
+try{oRequest.responseXML=fGetDocument(oRequest._object);}catch(e){}
+try{oRequest.status=oRequest._object.status;}catch(e){}
+try{oRequest.statusText=oRequest._object.statusText;}catch(e){}};function fCleanTransport(oRequest){oRequest._object.onreadystatechange=new window.Function;delete oRequest._headers;};if(!window.Function.prototype.apply){window.Function.prototype.apply=function(oRequest,oArguments){if(!oArguments)
+oArguments=[];oRequest.__func=this;oRequest.__func(oArguments[0],oArguments[1],oArguments[2],oArguments[3],oArguments[4]);delete oRequest.__func;};};OpenLayers.Request.XMLHttpRequest=cXMLHttpRequest;})();OpenLayers.Tile=OpenLayers.Class({EVENT_TYPES:["loadstart","loadend","reload","unload"],events:null,id:null,layer:null,url:null,bounds:null,size:null,position:null,isLoading:false,initialize:function(layer,position,bounds,url,size){this.layer=layer;this.position=position.clone();this.bounds=bounds.clone();this.url=url;this.size=size.clone();this.id=OpenLayers.Util.createUniqueID("Tile_");this.events=new OpenLayers.Events(this,null,this.EVENT_TYPES);},unload:function(){if(this.isLoading){this.isLoading=false;this.events.triggerEvent("unload");}},destroy:function(){this.layer=null;this.bounds=null;this.size=null;this.position=null;this.events.destroy();this.events=null;},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile(this.layer,this.position,this.bounds,this.url,this.size);}
+OpenLayers.Util.applyDefaults(obj,this);return obj;},draw:function(){var maxExtent=this.layer.maxExtent;var withinMaxExtent=(maxExtent&&this.bounds.intersectsBounds(maxExtent,false));this.shouldDraw=(withinMaxExtent||this.layer.displayOutsideMaxExtent);this.clear();return this.shouldDraw;},moveTo:function(bounds,position,redraw){if(redraw==null){redraw=true;}
 this.bounds=bounds.clone();this.position=position.clone();if(redraw){this.draw();}},clear:function(){},getBoundsFromBaseLayer:function(position){var msg=OpenLayers.i18n('reprojectDeprecated',{'layerName':this.layer.name});OpenLayers.Console.warn(msg);var topLeft=this.layer.map.getLonLatFromLayerPx(position);var bottomRightPx=position.clone();bottomRightPx.x+=this.size.w;bottomRightPx.y+=this.size.h;var bottomRight=this.layer.map.getLonLatFromLayerPx(bottomRightPx);if(topLeft.lon>bottomRight.lon){if(topLeft.lon<0){topLeft.lon=-180-(topLeft.lon+180);}else{bottomRight.lon=180+bottomRight.lon+180;}}
-var bounds=new OpenLayers.Bounds(topLeft.lon,bottomRight.lat,bottomRight.lon,topLeft.lat);return bounds;},startTransition:function(){},resetBackBuffer:function(){this.showTile();if(this.backBufferTile&&(this.isFirstDraw||!this.layer.numLoadingTiles)){this.isFirstDraw=false;var maxExtent=this.layer.maxExtent;var withinMaxExtent=(maxExtent&&this.bounds.intersectsBounds(maxExtent,false));if(withinMaxExtent){this.backBufferTile.position=this.position;this.backBufferTile.bounds=this.bounds;this.backBufferTile.size=this.size;this.backBufferTile.imageSize=this.layer.imageSize||this.size;this.backBufferTile.imageOffset=this.layer.imageOffset;this.backBufferTile.resolution=this.layer.getResolution();this.backBufferTile.renderTile();}}},showTile:function(){if(this.shouldDraw){this.show();}},show:function(){},hide:function(){},CLASS_NAME:"OpenLayers.Tile"});OpenLayers.Handler=OpenLayers.Class({id:null,control:null,map:null,keyMask:null,active:false,evt:null,initialize:function(control,callbacks,options){OpenLayers.Util.extend(this,options);this.control=control;this.callbacks=callbacks;if(control.map){this.setMap(control.map);}
+var bounds=new OpenLayers.Bounds(topLeft.lon,bottomRight.lat,bottomRight.lon,topLeft.lat);return bounds;},showTile:function(){if(this.shouldDraw){this.show();}},show:function(){},hide:function(){},CLASS_NAME:"OpenLayers.Tile"});OpenLayers.ProxyHost="";OpenLayers.nullHandler=function(request){OpenLayers.Console.userError(OpenLayers.i18n("unhandledRequest",{'statusText':request.statusText}));};OpenLayers.loadURL=function(uri,params,caller,onComplete,onFailure){if(typeof params=='string'){params=OpenLayers.Util.getParameters(params);}
+var success=(onComplete)?onComplete:OpenLayers.nullHandler;var failure=(onFailure)?onFailure:OpenLayers.nullHandler;return OpenLayers.Request.GET({url:uri,params:params,success:success,failure:failure,scope:caller});};OpenLayers.parseXMLString=function(text){var index=text.indexOf('<');if(index>0){text=text.substring(index);}
+var ajaxResponse=OpenLayers.Util.Try(function(){var xmldom=new ActiveXObject('Microsoft.XMLDOM');xmldom.loadXML(text);return xmldom;},function(){return new DOMParser().parseFromString(text,'text/xml');},function(){var req=new XMLHttpRequest();req.open("GET","data:"+"text/xml"+";charset=utf-8,"+encodeURIComponent(text),false);if(req.overrideMimeType){req.overrideMimeType("text/xml");}
+req.send(null);return req.responseXML;});return ajaxResponse;};OpenLayers.Ajax={emptyFunction:function(){},getTransport:function(){return OpenLayers.Util.Try(function(){return new XMLHttpRequest();},function(){return new ActiveXObject('Msxml2.XMLHTTP');},function(){return new ActiveXObject('Microsoft.XMLHTTP');})||false;},activeRequestCount:0};OpenLayers.Ajax.Responders={responders:[],register:function(responderToAdd){for(var i=0;i<this.responders.length;i++){if(responderToAdd==this.responders[i]){return;}}
+this.responders.push(responderToAdd);},unregister:function(responderToRemove){OpenLayers.Util.removeItem(this.reponders,responderToRemove);},dispatch:function(callback,request,transport){var responder;for(var i=0;i<this.responders.length;i++){responder=this.responders[i];if(responder[callback]&&typeof responder[callback]=='function'){try{responder[callback].apply(responder,[request,transport]);}catch(e){}}}}};OpenLayers.Ajax.Responders.register({onCreate:function(){OpenLayers.Ajax.activeRequestCount++;},onComplete:function(){OpenLayers.Ajax.activeRequestCount--;}});OpenLayers.Ajax.Base=OpenLayers.Class({initialize:function(options){this.options={method:'post',asynchronous:true,contentType:'application/xml',parameters:''};OpenLayers.Util.extend(this.options,options||{});this.options.method=this.options.method.toLowerCase();if(typeof this.options.parameters=='string'){this.options.parameters=OpenLayers.Util.getParameters(this.options.parameters);}}});OpenLayers.Ajax.Request=OpenLayers.Class(OpenLayers.Ajax.Base,{_complete:false,initialize:function(url,options){OpenLayers.Ajax.Base.prototype.initialize.apply(this,[options]);if(OpenLayers.ProxyHost&&OpenLayers.String.startsWith(url,"http")){url=OpenLayers.ProxyHost+encodeURIComponent(url);}
+this.transport=OpenLayers.Ajax.getTransport();this.request(url);},request:function(url){this.url=url;this.method=this.options.method;var params=OpenLayers.Util.extend({},this.options.parameters);if(this.method!='get'&&this.method!='post'){params['_method']=this.method;this.method='post';}
+this.parameters=params;if(params=OpenLayers.Util.getParameterString(params)){if(this.method=='get'){this.url+=((this.url.indexOf('?')>-1)?'&':'?')+params;}else if(/Konqueror|Safari|KHTML/.test(navigator.userAgent)){params+='&_=';}}
+try{var response=new OpenLayers.Ajax.Response(this);if(this.options.onCreate){this.options.onCreate(response);}
+OpenLayers.Ajax.Responders.dispatch('onCreate',this,response);this.transport.open(this.method.toUpperCase(),this.url,this.options.asynchronous);if(this.options.asynchronous){window.setTimeout(OpenLayers.Function.bind(this.respondToReadyState,this,1),10);}
+this.transport.onreadystatechange=OpenLayers.Function.bind(this.onStateChange,this);this.setRequestHeaders();this.body=this.method=='post'?(this.options.postBody||params):null;this.transport.send(this.body);if(!this.options.asynchronous&&this.transport.overrideMimeType){this.onStateChange();}}catch(e){this.dispatchException(e);}},onStateChange:function(){var readyState=this.transport.readyState;if(readyState>1&&!((readyState==4)&&this._complete)){this.respondToReadyState(this.transport.readyState);}},setRequestHeaders:function(){var headers={'X-Requested-With':'XMLHttpRequest','Accept':'text/javascript, text/html, application/xml, text/xml, */*','OpenLayers':true};if(this.method=='post'){headers['Content-type']=this.options.contentType+
+(this.options.encoding?'; charset='+this.options.encoding:'');if(this.transport.overrideMimeType&&(navigator.userAgent.match(/Gecko\/(\d{4})/)||[0,2005])[1]<2005){headers['Connection']='close';}}
+if(typeof this.options.requestHeaders=='object'){var extras=this.options.requestHeaders;if(typeof extras.push=='function'){for(var i=0,length=extras.length;i<length;i+=2){headers[extras[i]]=extras[i+1];}}else{for(var i in extras){headers[i]=extras[i];}}}
+for(var name in headers){this.transport.setRequestHeader(name,headers[name]);}},success:function(){var status=this.getStatus();return!status||(status>=200&&status<300);},getStatus:function(){try{return this.transport.status||0;}catch(e){return 0;}},respondToReadyState:function(readyState){var state=OpenLayers.Ajax.Request.Events[readyState];var response=new OpenLayers.Ajax.Response(this);if(state=='Complete'){try{this._complete=true;(this.options['on'+response.status]||this.options['on'+(this.success()?'Success':'Failure')]||OpenLayers.Ajax.emptyFunction)(response);}catch(e){this.dispatchException(e);}
+var contentType=response.getHeader('Content-type');}
+try{(this.options['on'+state]||OpenLayers.Ajax.emptyFunction)(response);OpenLayers.Ajax.Responders.dispatch('on'+state,this,response);}catch(e){this.dispatchException(e);}
+if(state=='Complete'){this.transport.onreadystatechange=OpenLayers.Ajax.emptyFunction;}},getHeader:function(name){try{return this.transport.getResponseHeader(name);}catch(e){return null;}},dispatchException:function(exception){var handler=this.options.onException;if(handler){handler(this,exception);OpenLayers.Ajax.Responders.dispatch('onException',this,exception);}else{var listener=false;var responders=OpenLayers.Ajax.Responders.responders;for(var i=0;i<responders.length;i++){if(responders[i].onException){listener=true;break;}}
+if(listener){OpenLayers.Ajax.Responders.dispatch('onException',this,exception);}else{throw exception;}}}});OpenLayers.Ajax.Request.Events=['Uninitialized','Loading','Loaded','Interactive','Complete'];OpenLayers.Ajax.Response=OpenLayers.Class({status:0,statusText:'',initialize:function(request){this.request=request;var transport=this.transport=request.transport,readyState=this.readyState=transport.readyState;if((readyState>2&&!(!!(window.attachEvent&&!window.opera)))||readyState==4){this.status=this.getStatus();this.statusText=this.getStatusText();this.responseText=transport.responseText==null?'':String(transport.responseText);}
+if(readyState==4){var xml=transport.responseXML;this.responseXML=xml===undefined?null:xml;}},getStatus:OpenLayers.Ajax.Request.prototype.getStatus,getStatusText:function(){try{return this.transport.statusText||'';}catch(e){return'';}},getHeader:OpenLayers.Ajax.Request.prototype.getHeader,getResponseHeader:function(name){return this.transport.getResponseHeader(name);}});OpenLayers.Ajax.getElementsByTagNameNS=function(parentnode,nsuri,nsprefix,tagname){var elem=null;if(parentnode.getElementsByTagNameNS){elem=parentnode.getElementsByTagNameNS(nsuri,tagname);}else{elem=parentnode.getElementsByTagName(nsprefix+':'+tagname);}
+return elem;};OpenLayers.Ajax.serializeXMLToString=function(xmldom){var serializer=new XMLSerializer();var data=serializer.serializeToString(xmldom);return data;};OpenLayers.Handler=OpenLayers.Class({id:null,control:null,map:null,keyMask:null,active:false,evt:null,initialize:function(control,callbacks,options){OpenLayers.Util.extend(this,options);this.control=control;this.callbacks=callbacks;if(control.map){this.setMap(control.map);}
 OpenLayers.Util.extend(this,options);this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},setMap:function(map){this.map=map;},checkModifiers:function(evt){if(this.keyMask==null){return true;}
 var keyModifiers=(evt.shiftKey?OpenLayers.Handler.MOD_SHIFT:0)|(evt.ctrlKey?OpenLayers.Handler.MOD_CTRL:0)|(evt.altKey?OpenLayers.Handler.MOD_ALT:0);return(keyModifiers==this.keyMask);},activate:function(){if(this.active){return false;}
-var events=OpenLayers.Events.prototype.BROWSER_EVENTS;for(var i=0;i<events.length;i++){if(this[events[i]]){this.register(events[i],this[events[i]]);}}
+var events=OpenLayers.Events.prototype.BROWSER_EVENTS;for(var i=0,len=events.length;i<len;i++){if(this[events[i]]){this.register(events[i],this[events[i]]);}}
 this.active=true;return true;},deactivate:function(){if(!this.active){return false;}
-var events=OpenLayers.Events.prototype.BROWSER_EVENTS;for(var i=0;i<events.length;i++){if(this[events[i]]){this.unregister(events[i],this[events[i]]);}}
-this.active=false;return true;},callback:function(name,args){if(name&&this.callbacks[name]){this.callbacks[name].apply(this.control,args);}},register:function(name,method){this.map.events.registerPriority(name,this,method);this.map.events.registerPriority(name,this,this.setEvent);},unregister:function(name,method){this.map.events.unregister(name,this,method);this.map.events.unregister(name,this,this.setEvent);},setEvent:function(evt){this.evt=evt;return true;},destroy:function(){this.deactivate();this.control=this.map=null;},CLASS_NAME:"OpenLayers.Handler"});OpenLayers.Handler.MOD_NONE=0;OpenLayers.Handler.MOD_SHIFT=1;OpenLayers.Handler.MOD_CTRL=2;OpenLayers.Handler.MOD_ALT=4;OpenLayers.Map=OpenLayers.Class({Z_INDEX_BASE:{BaseLayer:100,Overlay:325,Popup:750,Control:1000},EVENT_TYPES:["preaddlayer","addlayer","removelayer","changelayer","movestart","move","moveend","zoomend","popupopen","popupclose","addmarker","removemarker","clearmarkers","mouseover","mouseout","mousemove","dragstart","drag","dragend","changebaselayer"],id:null,fractionalZoom:false,events:null,div:null,dragging:false,size:null,viewPortDiv:null,layerContainerOrigin:null,layerContainerDiv:null,layers:null,controls:null,popups:null,baseLayer:null,center:null,resolution:null,zoom:0,viewRequestID:0,tileSize:null,projection:"EPSG:4326",units:'degrees',resolutions:null,maxResolution:1.40625,minResolution:null,maxScale:null,minScale:null,maxExtent:null,minExtent:null,restrictedExtent:null,numZoomLevels:16,theme:null,displayProjection:null,fallThrough:true,panTween:null,eventListeners:null,panMethod:OpenLayers.Easing.Expo.easeOut,paddingForPopups:null,initialize:function(div,options){this.tileSize=new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,OpenLayers.Map.TILE_HEIGHT);this.maxExtent=new OpenLayers.Bounds(-180,-90,180,90);this.paddingForPopups=new OpenLayers.Bounds(15,15,15,15);this.theme=OpenLayers._getScriptLocation()+'theme/default/style.css';OpenLayers.Util.extend(this,options);this.id=OpenLayers.Util.createUniqueID("OpenLayers.Map_");this.div=OpenLayers.Util.getElement(div);var id=this.div.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);id=this.div.id+"_OpenLayers_Container";this.layerContainerDiv=OpenLayers.Util.createDiv(id);this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;this.viewPortDiv.appendChild(this.layerContainerDiv);this.events=new OpenLayers.Events(this,this.div,this.EVENT_TYPES,this.fallThrough);this.updateSize();if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}
+var events=OpenLayers.Events.prototype.BROWSER_EVENTS;for(var i=0,len=events.length;i<len;i++){if(this[events[i]]){this.unregister(events[i],this[events[i]]);}}
+this.active=false;return true;},callback:function(name,args){if(name&&this.callbacks[name]){this.callbacks[name].apply(this.control,args);}},register:function(name,method){this.map.events.registerPriority(name,this,method);this.map.events.registerPriority(name,this,this.setEvent);},unregister:function(name,method){this.map.events.unregister(name,this,method);this.map.events.unregister(name,this,this.setEvent);},setEvent:function(evt){this.evt=evt;return true;},destroy:function(){this.deactivate();this.control=this.map=null;},CLASS_NAME:"OpenLayers.Handler"});OpenLayers.Handler.MOD_NONE=0;OpenLayers.Handler.MOD_SHIFT=1;OpenLayers.Handler.MOD_CTRL=2;OpenLayers.Handler.MOD_ALT=4;OpenLayers.Map=OpenLayers.Class({Z_INDEX_BASE:{BaseLayer:100,Overlay:325,Feature:725,Popup:750,Control:1000},EVENT_TYPES:["preaddlayer","addlayer","removelayer","changelayer","movestart","move","moveend","zoomend","popupopen","popupclose","addmarker","removemarker","clearmarkers","mouseover","mouseout","mousemove","dragstart","drag","dragend","changebaselayer"],id:null,fractionalZoom:false,events:null,div:null,dragging:false,size:null,viewPortDiv:null,layerContainerOrigin:null,layerContainerDiv:null,layers:null,controls:null,popups:null,baseLayer:null,center:null,resolution:null,zoom:0,panRatio:1.5,viewRequestID:0,tileSize:null,projection:"EPSG:4326",units:'degrees',resolutions:null,maxResolution:1.40625,minResolution:null,maxScale:null,minScale:null,maxExtent:null,minExtent:null,restrictedExtent:null,numZoomLevels:16,theme:null,displayProjection:null,fallThrough:true,panTween:null,eventListeners:null,panMethod:OpenLayers.Easing.Expo.easeOut,paddingForPopups:null,initialize:function(div,options){this.tileSize=new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,OpenLayers.Map.TILE_HEIGHT);this.maxExtent=new OpenLayers.Bounds(-180,-90,180,90);this.paddingForPopups=new OpenLayers.Bounds(15,15,15,15);this.theme=OpenLayers._getScriptLocation()+'theme/default/style.css';OpenLayers.Util.extend(this,options);this.id=OpenLayers.Util.createUniqueID("OpenLayers.Map_");this.div=OpenLayers.Util.getElement(div);OpenLayers.Element.addClass(this.div,'olMap');var id=this.div.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);id=this.div.id+"_OpenLayers_Container";this.layerContainerDiv=OpenLayers.Util.createDiv(id);this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;this.viewPortDiv.appendChild(this.layerContainerDiv);this.events=new OpenLayers.Events(this,this.div,this.EVENT_TYPES,this.fallThrough,{includeXY:true});this.updateSize();if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}
 this.events.register("movestart",this,this.updateSize);if(OpenLayers.String.contains(navigator.appName,"Microsoft")){this.events.register("resize",this,this.updateSize);}else{this.updateSizeDestroy=OpenLayers.Function.bind(this.updateSize,this);OpenLayers.Event.observe(window,'resize',this.updateSizeDestroy);}
-if(this.theme){var addNode=true;var nodes=document.getElementsByTagName('link');for(var i=0;i<nodes.length;++i){if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,this.theme)){addNode=false;break;}}
+if(this.theme){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;}}
 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);}}
 this.layers=[];if(this.controls==null){if(OpenLayers.Control!=null){this.controls=[new OpenLayers.Control.Navigation(),new OpenLayers.Control.PanZoom(),new OpenLayers.Control.ArgParser(),new OpenLayers.Control.Attribution()];}else{this.controls=[];}}
-for(var i=0;i<this.controls.length;i++){this.addControlToMap(this.controls[i]);}
+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);OpenLayers.Event.observe(window,'unload',this.unloadDestroy);},unloadDestroy:null,updateSizeDestroy:null,destroy:function(){if(!this.unloadDestroy){return false;}
 OpenLayers.Event.stopObserving(window,'unload',this.unloadDestroy);this.unloadDestroy=null;if(this.updateSizeDestroy){OpenLayers.Event.stopObserving(window,'resize',this.updateSizeDestroy);}else{this.events.unregister("resize",this,this.updateSize);}
 this.paddingForPopups=null;if(this.controls!=null){for(var i=this.controls.length-1;i>=0;--i){this.controls[i].destroy();}
@@ -308,38 +388,38 @@
 this.layers=null;}
 if(this.viewPortDiv){this.div.removeChild(this.viewPortDiv);}
 this.viewPortDiv=null;if(this.eventListeners){this.events.un(this.eventListeners);this.eventListeners=null;}
-this.events.destroy();this.events=null;},setOptions:function(options){OpenLayers.Util.extend(this,options);},getTileSize:function(){return this.tileSize;},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;},getLayersBy:function(property,match){return this.getBy("layers",property,match);},getLayersByName:function(match){return this.getLayersBy("name",match);},getLayersByClass:function(match){return this.getLayersBy("CLASS_NAME",match);},getControlsBy:function(property,match){return this.getBy("controls",property,match);},getControlsByClass:function(match){return this.getControlsBy("CLASS_NAME",match);},getLayer:function(id){var foundLayer=null;for(var i=0;i<this.layers.length;i++){var layer=this.layers[i];if(layer.id==id){foundLayer=layer;break;}}
+this.events.destroy();this.events=null;},setOptions:function(options){OpenLayers.Util.extend(this,options);},getTileSize:function(){return this.tileSize;},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;},getLayersBy:function(property,match){return this.getBy("layers",property,match);},getLayersByName:function(match){return this.getLayersBy("name",match);},getLayersByClass:function(match){return this.getLayersBy("CLASS_NAME",match);},getControlsBy:function(property,match){return this.getBy("controls",property,match);},getControlsByClass:function(match){return this.getControlsBy("CLASS_NAME",match);},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;},setLayerZIndex:function(layer,zIdx){layer.setZIndex(this.Z_INDEX_BASE[layer.isBaseLayer?'BaseLayer':'Overlay']
-+zIdx*5);},resetLayersZIndex:function(){for(var i=0;i<this.layers.length;i++){var layer=this.layers[i];this.setLayerZIndex(layer,i);}},addLayer:function(layer){for(var i=0;i<this.layers.length;i++){if(this.layers[i]==layer){var msg=OpenLayers.i18n('layerAlreadyAdded',{'layerName':layer.name});OpenLayers.Console.warn(msg);return false;}}
++zIdx*5);},resetLayersZIndex:function(){for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];this.setLayerZIndex(layer,i);}},addLayer:function(layer){for(var i=0,len=this.layers.length;i<len;i++){if(this.layers[i]==layer){var msg=OpenLayers.i18n('layerAlreadyAdded',{'layerName':layer.name});OpenLayers.Console.warn(msg);return false;}}
 this.events.triggerEvent("preaddlayer",{layer:layer});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){if(this.baseLayer==null){this.setBaseLayer(layer);}else{layer.setVisibility(false);}}else{layer.redraw();}
-this.events.triggerEvent("addlayer",{layer:layer});},addLayers:function(layers){for(var i=0;i<layers.length;i++){this.addLayer(layers[i]);}},removeLayer:function(layer,setNewBaseLayer){if(setNewBaseLayer==null){setNewBaseLayer=true;}
+this.events.triggerEvent("addlayer",{layer:layer});},addLayers:function(layers){for(var i=0,len=layers.length;i<len;i++){this.addLayer(layers[i]);}},removeLayer:function(layer,setNewBaseLayer){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(this.baseLayer==layer){this.baseLayer=null;if(setNewBaseLayer){for(var i=0;i<this.layers.length;i++){var iLayer=this.layers[i];if(iLayer.isBaseLayer){this.setBaseLayer(iLayer);break;}}}}
+OpenLayers.Util.removeItem(this.layers,layer);layer.removeMap(this);layer.map=null;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.setBaseLayer(iLayer);break;}}}}
 this.resetLayersZIndex();this.events.triggerEvent("removelayer",{layer:layer});},getNumLayers:function(){return this.layers.length;},getLayerIndex:function(layer){return OpenLayers.Util.indexOf(this.layers,layer);},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;i<this.layers.length;i++){this.setLayerZIndex(this.layers[i],i);}
+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"});}},raiseLayer:function(layer,delta){var idx=this.getLayerIndex(layer)+delta;this.setLayerIndex(layer,idx);},setBaseLayer:function(newBaseLayer){var oldExtent=null;if(this.baseLayer){oldExtent=this.baseLayer.getExtent();}
 if(newBaseLayer!=this.baseLayer){if(OpenLayers.Util.indexOf(this.layers,newBaseLayer)!=-1){if(this.baseLayer!=null){this.baseLayer.setVisibility(false);}
 this.baseLayer=newBaseLayer;this.viewRequestID++;this.baseLayer.visibility=true;var center=this.getCenter();if(center!=null){var newCenter=(oldExtent)?oldExtent.getCenterLonLat():center;var newZoom=(oldExtent)?this.getZoomForExtent(oldExtent,true):this.getZoomForResolution(this.resolution,true);this.setCenter(newCenter,newZoom,false,true);}
 this.events.triggerEvent("changebaselayer",{layer:this.baseLayer});}}},addControl:function(control,px){this.controls.push(control);this.addControlToMap(control,px);},addControlToMap:function(control,px){control.outsideViewport=(control.div!=null);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);}}},getControl:function(id){var returnControl=null;for(var i=0;i<this.controls.length;i++){var control=this.controls[i];if(control.id==id){returnControl=control;break;}}
+this.controls.length;this.viewPortDiv.appendChild(div);}}},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;},removeControl:function(control){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);}},addPopup:function(popup,exclusive){if(exclusive){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);}},removePopup:function(popup){OpenLayers.Util.removeItem(this.popups,popup);if(popup.div){try{this.layerContainerDiv.removeChild(popup.div);}
 catch(e){}}
 popup.map=null;},getSize:function(){var size=null;if(this.size!=null){size=this.size.clone();}
-return size;},updateSize:function(){this.events.element.offsets=null;var newSize=this.getCurrentSize();var oldSize=this.getSize();if(oldSize==null){this.size=oldSize=newSize;}
-if(!newSize.equals(oldSize)){this.size=newSize;for(var i=0;i<this.layers.length;i++){this.layers[i].onMapResize();}
+return size;},updateSize:function(){this.events.clearMouseCache();var newSize=this.getCurrentSize();var oldSize=this.getSize();if(oldSize==null){this.size=oldSize=newSize;}
+if(!newSize.equals(oldSize)){this.size=newSize;for(var i=0,len=this.layers.length;i<len;i++){this.layers[i].onMapResize();}
 if(this.baseLayer!=null){var center=new OpenLayers.Pixel(newSize.w/2,newSize.h/2);var centerLL=this.getLonLatFromViewPortPx(center);var zoom=this.getZoom();this.zoom=null;this.setCenter(this.getCenter(),zoom);}}},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)){var dim=OpenLayers.Element.getDimensions(this.div);size.w=dim.width;size.h=dim.height;}
 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;},calculateBounds:function(center,resolution){var extent=null;if(center==null){center=this.getCenter();}
 if(resolution==null){resolution=this.getResolution();}
 if((center!=null)&&(resolution!=null)){var size=this.getSize();var w_deg=size.w*resolution;var h_deg=size.h*resolution;extent=new OpenLayers.Bounds(center.lon-w_deg/2,center.lat-h_deg/2,center.lon+w_deg/2,center.lat+h_deg/2);}
-return extent;},getCenter:function(){return this.center;},getZoom:function(){return this.zoom;},pan:function(dx,dy,options){if(!options){options={};}
-OpenLayers.Util.applyDefaults(options,{animate:true,dragging:false});var centerPx=this.getViewPortPxFromLonLat(this.getCenter());var newCenterPx=centerPx.add(dx,dy);if(!options.dragging||!newCenterPx.equals(centerPx)){var newCenterLonLat=this.getLonLatFromViewPortPx(newCenterPx);if(options.animate){this.panTo(newCenterLonLat);}else{this.setCenter(newCenterLonLat,null,options.dragging);}}},panTo:function(lonlat){if(this.panMethod&&this.getExtent().containsLonLat(lonlat)){if(!this.panTween){this.panTween=new OpenLayers.Tween(this.panMethod);}
-var center=this.getCenter();var from={lon:center.lon,lat:center.lat};var to={lon:lonlat.lon,lat:lonlat.lat};this.panTween.start(from,to,50,{callbacks:{start:OpenLayers.Function.bind(function(lonlat){this.events.triggerEvent("movestart");},this),eachStep:OpenLayers.Function.bind(function(lonlat){lonlat=new OpenLayers.LonLat(lonlat.lon,lonlat.lat);this.moveTo(lonlat,this.zoom,{'dragging':true,'noEvent':true});},this),done:OpenLayers.Function.bind(function(lonlat){lonlat=new OpenLayers.LonLat(lonlat.lon,lonlat.lat);this.moveTo(lonlat,this.zoom,{'noEvent':true});this.events.triggerEvent("moveend");},this)}});}else{this.setCenter(lonlat);}},setCenter:function(lonlat,zoom,dragging,forceZoomChange){this.moveTo(lonlat,zoom,{'dragging':dragging,'forceZoomChange':forceZoomChange,'caller':'setCenter'});},moveTo:function(lonlat,zoom,options){if(!options){options={};}
+return extent;},getCenter:function(){return this.center;},getZoom:function(){return this.zoom;},pan:function(dx,dy,options){options=OpenLayers.Util.applyDefaults(options,{animate:true,dragging:false});var centerPx=this.getViewPortPxFromLonLat(this.getCenter());var newCenterPx=centerPx.add(dx,dy);if(!options.dragging||!newCenterPx.equals(centerPx)){var newCenterLonLat=this.getLonLatFromViewPortPx(newCenterPx);if(options.animate){this.panTo(newCenterLonLat);}else{this.setCenter(newCenterLonLat,null,options.dragging);}}},panTo:function(lonlat){if(this.panMethod&&this.getExtent().scale(this.panRatio).containsLonLat(lonlat)){if(!this.panTween){this.panTween=new OpenLayers.Tween(this.panMethod);}
+var center=this.getCenter();if(lonlat.lon==center.lon&&lonlat.lat==center.lat){return;}
+var from={lon:center.lon,lat:center.lat};var to={lon:lonlat.lon,lat:lonlat.lat};this.panTween.start(from,to,50,{callbacks:{start:OpenLayers.Function.bind(function(lonlat){this.events.triggerEvent("movestart");},this),eachStep:OpenLayers.Function.bind(function(lonlat){lonlat=new OpenLayers.LonLat(lonlat.lon,lonlat.lat);this.moveTo(lonlat,this.zoom,{'dragging':true,'noEvent':true});},this),done:OpenLayers.Function.bind(function(lonlat){lonlat=new OpenLayers.LonLat(lonlat.lon,lonlat.lat);this.moveTo(lonlat,this.zoom,{'noEvent':true});this.events.triggerEvent("moveend");},this)}});}else{this.setCenter(lonlat);}},setCenter:function(lonlat,zoom,dragging,forceZoomChange){this.moveTo(lonlat,zoom,{'dragging':dragging,'forceZoomChange':forceZoomChange,'caller':'setCenter'});},moveTo:function(lonlat,zoom,options){if(!options){options={};}
 var dragging=options.dragging;var forceZoomChange=options.forceZoomChange;var noEvent=options.noEvent;if(this.panTween&&options.caller=="setCenter"){this.panTween.stop();}
 if(!this.center&&!this.isValidLonLat(lonlat)){lonlat=this.maxExtent.getCenterLonLat();}
 if(this.restrictedExtent!=null){if(lonlat==null){lonlat=this.getCenter();}
@@ -356,41 +436,48 @@
 this.center=lonlat.clone();}
 if((zoomChanged)||(this.layerContainerOrigin==null)){this.layerContainerOrigin=this.center.clone();this.layerContainerDiv.style.left="0px";this.layerContainerDiv.style.top="0px";}
 if(zoomChanged){this.zoom=zoom;this.resolution=this.getResolutionForZoom(zoom);this.viewRequestID++;}
-var bounds=this.getExtent();this.baseLayer.moveTo(bounds,zoomChanged,dragging);bounds=this.baseLayer.getExtent();for(var i=0;i<this.layers.length;i++){var layer=this.layers[i];if(!layer.isBaseLayer){var inRange=layer.calculateInRange();if(layer.inRange!=inRange){layer.inRange=inRange;if(!inRange){layer.display(false);}
+var bounds=this.getExtent();this.baseLayer.moveTo(bounds,zoomChanged,dragging);bounds=this.baseLayer.getExtent();for(var i=0,len=this.layers.length;i<len;i++){var layer=this.layers[i];if(!layer.isBaseLayer){var inRange=layer.calculateInRange();if(layer.inRange!=inRange){layer.inRange=inRange;if(!inRange){layer.display(false);}
 this.events.triggerEvent("changelayer",{layer:layer,property:"visibility"});}
-if(inRange&&layer.visibility){layer.moveTo(bounds,zoomChanged,dragging);}}}
-if(zoomChanged){for(var i=0;i<this.popups.length;i++){this.popups[i].updatePosition();}}
+if(inRange&&layer.visibility){layer.moveTo(bounds,zoomChanged,dragging);layer.events.triggerEvent("moveend",{"zoomChanged":zoomChanged});}}}
+if(zoomChanged){for(var i=0,len=this.popups.length;i<len;i++){this.popups[i].updatePosition();}}
 this.events.triggerEvent("move");if(zoomChanged){this.events.triggerEvent("zoomend");}}
 if(!dragging&&!noEvent){this.events.triggerEvent("moveend");}
 this.dragging=!!dragging;},centerLayerContainer:function(lonlat){var originPx=this.getViewPortPxFromLonLat(this.layerContainerOrigin);var newPx=this.getViewPortPxFromLonLat(lonlat);if((originPx!=null)&&(newPx!=null)){this.layerContainerDiv.style.left=Math.round(originPx.x-newPx.x)+"px";this.layerContainerDiv.style.top=Math.round(originPx.y-newPx.y)+"px";}},isValidZoomLevel:function(zoomLevel){return((zoomLevel!=null)&&(zoomLevel>=0)&&(zoomLevel<this.getNumZoomLevels()));},isValidLonLat:function(lonlat){var valid=false;if(lonlat!=null){var maxExtent=this.getMaxExtent();valid=maxExtent.containsLonLat(lonlat);}
 return valid;},getProjection:function(){var projection=this.getProjectionObject();return projection?projection.getCode():null;},getProjectionObject:function(){var projection=null;if(this.baseLayer!=null){projection=this.baseLayer.projection;}
 return projection;},getMaxResolution:function(){var maxResolution=null;if(this.baseLayer!=null){maxResolution=this.baseLayer.maxResolution;}
-return maxResolution;},getMaxExtent:function(){var maxExtent=null;if(this.baseLayer!=null){maxExtent=this.baseLayer.maxExtent;}
+return maxResolution;},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;},getNumZoomLevels:function(){var numZoomLevels=null;if(this.baseLayer!=null){numZoomLevels=this.baseLayer.numZoomLevels;}
 return numZoomLevels;},getExtent:function(){var extent=null;if(this.baseLayer!=null){extent=this.baseLayer.getExtent();}
 return extent;},getResolution:function(){var resolution=null;if(this.baseLayer!=null){resolution=this.baseLayer.getResolution();}
-return resolution;},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 resolution;},getUnits:function(){var units=null;if(this.baseLayer!=null){units=this.baseLayer.units;}
+return units;},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;},getZoomForExtent:function(bounds,closest){var zoom=null;if(this.baseLayer!=null){zoom=this.baseLayer.getZoomForExtent(bounds,closest);}
 return zoom;},getResolutionForZoom:function(zoom){var resolution=null;if(this.baseLayer){resolution=this.baseLayer.getResolutionForZoom(zoom);}
 return resolution;},getZoomForResolution:function(resolution,closest){var zoom=null;if(this.baseLayer!=null){zoom=this.baseLayer.getZoomForResolution(resolution,closest);}
-return zoom;},zoomTo:function(zoom){if(this.isValidZoomLevel(zoom)){this.setCenter(null,zoom);}},zoomIn:function(){this.zoomTo(this.getZoom()+1);},zoomOut:function(){this.zoomTo(this.getZoom()-1);},zoomToExtent:function(bounds){var center=bounds.getCenterLonLat();if(this.baseLayer.wrapDateLine){var maxExtent=this.getMaxExtent();bounds=bounds.clone();while(bounds.right<bounds.left){bounds.right+=maxExtent.getWidth();}
+return zoom;},zoomTo:function(zoom){if(this.isValidZoomLevel(zoom)){this.setCenter(null,zoom);}},zoomIn:function(){this.zoomTo(this.getZoom()+1);},zoomOut:function(){this.zoomTo(this.getZoom()-1);},zoomToExtent:function(bounds,closest){var center=bounds.getCenterLonLat();if(this.baseLayer.wrapDateLine){var maxExtent=this.getMaxExtent();bounds=bounds.clone();while(bounds.right<bounds.left){bounds.right+=maxExtent.getWidth();}
 center=bounds.getCenterLonLat().wrapDateLine(maxExtent);}
-this.setCenter(center,this.getZoomForExtent(bounds));},zoomToMaxExtent:function(){this.zoomToExtent(this.getMaxExtent());},zoomToScale:function(scale){var res=OpenLayers.Util.getResolutionFromScale(scale,this.baseLayer.units);var size=this.getSize();var w_deg=size.w*res;var h_deg=size.h*res;var center=this.getCenter();var extent=new OpenLayers.Bounds(center.lon-w_deg/2,center.lat-h_deg/2,center.lon+w_deg/2,center.lat+h_deg/2);this.zoomToExtent(extent);},getLonLatFromViewPortPx:function(viewPortPx){var lonlat=null;if(this.baseLayer!=null){lonlat=this.baseLayer.getLonLatFromViewPortPx(viewPortPx);}
+this.setCenter(center,this.getZoomForExtent(bounds,closest));},zoomToMaxExtent:function(options){var restricted=(options)?options.restricted:true;var maxExtent=this.getMaxExtent({'restricted':restricted});this.zoomToExtent(maxExtent);},zoomToScale:function(scale,closest){var res=OpenLayers.Util.getResolutionFromScale(scale,this.baseLayer.units);var size=this.getSize();var w_deg=size.w*res;var h_deg=size.h*res;var center=this.getCenter();var extent=new OpenLayers.Bounds(center.lon-w_deg/2,center.lat-h_deg/2,center.lon+w_deg/2,center.lat+h_deg/2);this.zoomToExtent(extent,closest);},getLonLatFromViewPortPx:function(viewPortPx){var lonlat=null;if(this.baseLayer!=null){lonlat=this.baseLayer.getLonLatFromViewPortPx(viewPortPx);}
 return lonlat;},getViewPortPxFromLonLat:function(lonlat){var px=null;if(this.baseLayer!=null){px=this.baseLayer.getViewPortPxFromLonLat(lonlat);}
 return px;},getLonLatFromPixel:function(px){return this.getLonLatFromViewPortPx(px);},getPixelFromLonLat:function(lonlat){var px=this.getViewPortPxFromLonLat(lonlat);px.x=Math.round(px.x);px.y=Math.round(px.y);return px;},getViewPortPxFromLayerPx:function(layerPx){var viewPortPx=null;if(layerPx!=null){var dX=parseInt(this.layerContainerDiv.style.left);var dY=parseInt(this.layerContainerDiv.style.top);viewPortPx=layerPx.add(dX,dY);}
 return viewPortPx;},getLayerPxFromViewPortPx:function(viewPortPx){var layerPx=null;if(viewPortPx!=null){var dX=-parseInt(this.layerContainerDiv.style.left);var dY=-parseInt(this.layerContainerDiv.style.top);layerPx=viewPortPx.add(dX,dY);if(isNaN(layerPx.x)||isNaN(layerPx.y)){layerPx=null;}}
 return layerPx;},getLonLatFromLayerPx:function(px){px=this.getViewPortPxFromLayerPx(px);return this.getLonLatFromViewPortPx(px);},getLayerPxFromLonLat:function(lonlat){var px=this.getPixelFromLonLat(lonlat);return this.getLayerPxFromViewPortPx(px);},CLASS_NAME:"OpenLayers.Map"});OpenLayers.Map.TILE_WIDTH=256;OpenLayers.Map.TILE_HEIGHT=256;OpenLayers.Marker=OpenLayers.Class({icon:null,lonlat:null,events:null,map:null,initialize:function(lonlat,icon){this.lonlat=lonlat;var newIcon=(icon)?icon:OpenLayers.Marker.defaultIcon();if(this.icon==null){this.icon=newIcon;}else{this.icon.url=newIcon.url;this.icon.size=newIcon.size;this.icon.offset=newIcon.offset;this.icon.calculateOffset=newIcon.calculateOffset;}
 this.events=new OpenLayers.Events(this,this.icon.imageDiv,null);},destroy:function(){this.map=null;this.events.destroy();this.events=null;if(this.icon!=null){this.icon.destroy();this.icon=null;}},draw:function(px){return this.icon.draw(px);},moveTo:function(px){if((px!=null)&&(this.icon!=null)){this.icon.moveTo(px);}
 this.lonlat=this.map.getLonLatFromLayerPx(px);},onScreen:function(){var onScreen=false;if(this.map){var screenBounds=this.map.getExtent();onScreen=screenBounds.containsLonLat(this.lonlat);}
-return onScreen;},inflate:function(inflate){if(this.icon){var newSize=new OpenLayers.Size(this.icon.size.w*inflate,this.icon.size.h*inflate);this.icon.setSize(newSize);}},setOpacity:function(opacity){this.icon.setOpacity(opacity);},setUrl:function(url){this.icon.setUrl(url);},display:function(display){this.icon.display(display);},CLASS_NAME:"OpenLayers.Marker"});OpenLayers.Marker.defaultIcon=function(){var url=OpenLayers.Util.getImagesLocation()+"marker.png";var size=new OpenLayers.Size(21,25);var calculateOffset=function(size){return new OpenLayers.Pixel(-(size.w/2),-size.h);};return new OpenLayers.Icon(url,size,null,calculateOffset);};OpenLayers.Tile.Image=OpenLayers.Class(OpenLayers.Tile,{url:null,imgDiv:null,frame:null,layerAlphaHack:null,initialize:function(layer,position,bounds,url,size){OpenLayers.Tile.prototype.initialize.apply(this,arguments);this.url=url;this.frame=document.createElement('div');this.frame.style.overflow='hidden';this.frame.style.position='absolute';this.layerAlphaHack=this.layer.alpha&&OpenLayers.Util.alphaHack();},destroy:function(){if(this.imgDiv!=null){if(this.layerAlphaHack){OpenLayers.Event.stopObservingElement(this.imgDiv.childNodes[0].id);}else{OpenLayers.Event.stopObservingElement(this.imgDiv.id);}
-if(this.imgDiv.parentNode==this.frame){this.frame.removeChild(this.imgDiv);this.imgDiv.map=null;}}
+return onScreen;},inflate:function(inflate){if(this.icon){var newSize=new OpenLayers.Size(this.icon.size.w*inflate,this.icon.size.h*inflate);this.icon.setSize(newSize);}},setOpacity:function(opacity){this.icon.setOpacity(opacity);},setUrl:function(url){this.icon.setUrl(url);},display:function(display){this.icon.display(display);},CLASS_NAME:"OpenLayers.Marker"});OpenLayers.Marker.defaultIcon=function(){var url=OpenLayers.Util.getImagesLocation()+"marker.png";var size=new OpenLayers.Size(21,25);var calculateOffset=function(size){return new OpenLayers.Pixel(-(size.w/2),-size.h);};return new OpenLayers.Icon(url,size,null,calculateOffset);};OpenLayers.Popup.AnchoredBubble=OpenLayers.Class(OpenLayers.Popup.Anchored,{rounded:false,initialize:function(id,lonlat,contentSize,contentHTML,anchor,closeBox,closeBoxCallback){this.padding=new OpenLayers.Bounds(0,OpenLayers.Popup.AnchoredBubble.CORNER_SIZE,0,OpenLayers.Popup.AnchoredBubble.CORNER_SIZE);OpenLayers.Popup.Anchored.prototype.initialize.apply(this,arguments);},draw:function(px){OpenLayers.Popup.Anchored.prototype.draw.apply(this,arguments);this.setContentHTML();this.setBackgroundColor();this.setOpacity();return this.div;},updateRelativePosition:function(){this.setRicoCorners();},setSize:function(contentSize){OpenLayers.Popup.Anchored.prototype.setSize.apply(this,arguments);this.setRicoCorners();},setBackgroundColor:function(color){if(color!=undefined){this.backgroundColor=color;}
+if(this.div!=null){if(this.contentDiv!=null){this.div.style.background="transparent";OpenLayers.Rico.Corner.changeColor(this.groupDiv,this.backgroundColor);}}},setOpacity:function(opacity){OpenLayers.Popup.Anchored.prototype.setOpacity.call(this,opacity);if(this.div!=null){if(this.groupDiv!=null){OpenLayers.Rico.Corner.changeOpacity(this.groupDiv,this.opacity);}}},setBorder:function(border){this.border=0;},setRicoCorners:function(){var corners=this.getCornersToRound(this.relativePosition);var options={corners:corners,color:this.backgroundColor,bgColor:"transparent",blend:false};if(!this.rounded){OpenLayers.Rico.Corner.round(this.div,options);this.rounded=true;}else{OpenLayers.Rico.Corner.reRound(this.groupDiv,options);this.setBackgroundColor();this.setOpacity();}},getCornersToRound:function(){var corners=['tl','tr','bl','br'];var corner=OpenLayers.Bounds.oppositeQuadrant(this.relativePosition);OpenLayers.Util.removeItem(corners,corner);return corners.join(" ");},CLASS_NAME:"OpenLayers.Popup.AnchoredBubble"});OpenLayers.Popup.AnchoredBubble.CORNER_SIZE=5;OpenLayers.Tile.Image=OpenLayers.Class(OpenLayers.Tile,{url:null,imgDiv:null,frame:null,layerAlphaHack:null,isBackBuffer:false,lastRatio:1,isFirstDraw:true,backBufferTile:null,initialize:function(layer,position,bounds,url,size){OpenLayers.Tile.prototype.initialize.apply(this,arguments);this.url=url;this.frame=document.createElement('div');this.frame.style.overflow='hidden';this.frame.style.position='absolute';this.layerAlphaHack=this.layer.alpha&&OpenLayers.Util.alphaHack();},destroy:function(){if(this.imgDiv!=null){if(this.layerAlphaHack){OpenLayers.Event.stopObservingElement(this.imgDiv.childNodes[0].id);}else{OpenLayers.Event.stopObservingElement(this.imgDiv.id);}
+if(this.imgDiv.parentNode==this.frame){this.frame.removeChild(this.imgDiv);this.imgDiv.map=null;}
+this.imgDiv.urls=null;}
 this.imgDiv=null;if((this.frame!=null)&&(this.frame.parentNode==this.layer.div)){this.layer.div.removeChild(this.frame);}
-this.frame=null;OpenLayers.Tile.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile.Image(this.layer,this.position,this.bounds,this.url,this.size);}
+this.frame=null;if(this.backBufferTile){this.backBufferTile.destroy();this.backBufferTile=null;}
+this.layer.events.unregister("loadend",this,this.resetBackBuffer);OpenLayers.Tile.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Tile.Image(this.layer,this.position,this.bounds,this.url,this.size);}
 obj=OpenLayers.Tile.prototype.clone.apply(this,[obj]);obj.imgDiv=null;return obj;},draw:function(){if(this.layer!=this.layer.map.baseLayer&&this.layer.reproject){this.bounds=this.getBoundsFromBaseLayer(this.position);}
-if(!OpenLayers.Tile.prototype.draw.apply(this,arguments)){return false;}
+var drawTile=OpenLayers.Tile.prototype.draw.apply(this,arguments);if(OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1){if(drawTile){if(!this.backBufferTile){this.backBufferTile=this.clone();this.backBufferTile.hide();this.backBufferTile.isBackBuffer=true;this.events.register('loadend',this,this.resetBackBuffer);this.layer.events.register("loadend",this,this.resetBackBuffer);}
+this.startTransition();}else{if(this.backBufferTile){this.backBufferTile.clear();}}}else{if(drawTile&&this.isFirstDraw){this.events.register('loadend',this,this.showTile);this.isFirstDraw=false;}}
+if(!drawTile){return false;}
 if(this.isLoading){this.events.triggerEvent("reload");}else{this.isLoading=true;this.events.triggerEvent("loadstart");}
-return this.renderTile();},renderTile:function(){if(this.imgDiv==null){this.initImgDiv();}
-this.imgDiv.viewRequestID=this.layer.map.viewRequestID;this.url=this.layer.getURL(this.bounds);OpenLayers.Util.modifyDOMElement(this.frame,null,this.position,this.size);var imageSize=this.layer.getImageSize();if(this.layerAlphaHack){OpenLayers.Util.modifyAlphaImageDiv(this.imgDiv,null,null,imageSize,this.url);}else{OpenLayers.Util.modifyDOMElement(this.imgDiv,null,null,imageSize);this.imgDiv.src=this.url;}
+return this.renderTile();},resetBackBuffer:function(){this.showTile();if(this.backBufferTile&&(this.isFirstDraw||!this.layer.numLoadingTiles)){this.isFirstDraw=false;var maxExtent=this.layer.maxExtent;var withinMaxExtent=(maxExtent&&this.bounds.intersectsBounds(maxExtent,false));if(withinMaxExtent){this.backBufferTile.position=this.position;this.backBufferTile.bounds=this.bounds;this.backBufferTile.size=this.size;this.backBufferTile.imageSize=this.layer.imageSize||this.size;this.backBufferTile.imageOffset=this.layer.imageOffset;this.backBufferTile.resolution=this.layer.getResolution();this.backBufferTile.renderTile();}}},renderTile:function(){if(this.imgDiv==null){this.initImgDiv();}
+this.imgDiv.viewRequestID=this.layer.map.viewRequestID;if(this.layer.url instanceof Array){this.imgDiv.urls=this.layer.url.slice();}
+this.url=this.layer.getURL(this.bounds);OpenLayers.Util.modifyDOMElement(this.frame,null,this.position,this.size);var imageSize=this.layer.getImageSize();if(this.layerAlphaHack){OpenLayers.Util.modifyAlphaImageDiv(this.imgDiv,null,null,imageSize,this.url);}else{OpenLayers.Util.modifyDOMElement(this.imgDiv,null,null,imageSize);this.imgDiv.src=this.url;}
 return true;},clear:function(){if(this.imgDiv){this.hide();if(OpenLayers.Tile.Image.useBlankTile){this.imgDiv.src=OpenLayers.Util.getImagesLocation()+"blank.gif";}}},initImgDiv:function(){var offset=this.layer.imageOffset;var size=this.layer.getImageSize();if(this.layerAlphaHack){this.imgDiv=OpenLayers.Util.createAlphaImageDiv(null,offset,size,null,"relative",null,null,null,true);}else{this.imgDiv=OpenLayers.Util.createImage(null,offset,size,null,"relative",null,null,true);}
 this.imgDiv.className='olTileImage';this.frame.style.zIndex=this.isBackBuffer?0:1;this.frame.appendChild(this.imgDiv);this.layer.div.appendChild(this.frame);if(this.layer.opacity!=null){OpenLayers.Util.modifyDOMElement(this.imgDiv,null,null,null,null,null,null,this.layer.opacity);}
 this.imgDiv.map=this.layer.map;var onload=function(){if(this.isLoading){this.isLoading=false;this.events.triggerEvent("loadend");}};if(this.layerAlphaHack){OpenLayers.Event.observe(this.imgDiv.childNodes[0],'load',OpenLayers.Function.bind(onload,this));}else{OpenLayers.Event.observe(this.imgDiv,'load',OpenLayers.Function.bind(onload,this));}
@@ -398,34 +485,48 @@
 var ratio=1;if(this.backBufferTile.resolution){ratio=this.backBufferTile.resolution/this.layer.getResolution();}
 if(ratio!=this.lastRatio){if(this.layer.transitionEffect=='resize'){var upperLeft=new OpenLayers.LonLat(this.backBufferTile.bounds.left,this.backBufferTile.bounds.top);var size=new OpenLayers.Size(this.backBufferTile.size.w*ratio,this.backBufferTile.size.h*ratio);var px=this.layer.map.getLayerPxFromLonLat(upperLeft);OpenLayers.Util.modifyDOMElement(this.backBufferTile.frame,null,px,size);var imageSize=this.backBufferTile.imageSize;imageSize=new OpenLayers.Size(imageSize.w*ratio,imageSize.h*ratio);var imageOffset=this.backBufferTile.imageOffset;if(imageOffset){imageOffset=new OpenLayers.Pixel(imageOffset.x*ratio,imageOffset.y*ratio);}
 OpenLayers.Util.modifyDOMElement(this.backBufferTile.imgDiv,null,imageOffset,imageSize);this.backBufferTile.show();}}else{if(this.layer.singleTile){this.backBufferTile.show();}else{this.backBufferTile.hide();}}
-this.lastRatio=ratio;},show:function(){this.frame.style.display='';if(OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1){if(navigator.userAgent.toLowerCase().indexOf("gecko")!=-1){this.frame.scrollLeft=this.frame.scrollLeft;}}},hide:function(){this.frame.style.display='none';},CLASS_NAME:"OpenLayers.Tile.Image"});OpenLayers.Tile.Image.useBlankTile=(OpenLayers.Util.getBrowserName()=="safari"||OpenLayers.Util.getBrowserName()=="opera");OpenLayers.Control.OverviewMap=OpenLayers.Class(OpenLayers.Control,{element:null,ovmap:null,size:new OpenLayers.Size(180,90),layers:null,minRectSize:15,minRectDisplayClass:"RectReplacement",minRatio:8,maxRatio:32,mapOptions:null,handlers:null,initialize:function(options){this.layers=[];this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,[options]);},destroy:function(){if(!this.mapDiv){return;}
+this.lastRatio=ratio;},show:function(){this.frame.style.display='';if(OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS,this.layer.transitionEffect)!=-1){if(navigator.userAgent.toLowerCase().indexOf("gecko")!=-1){this.frame.scrollLeft=this.frame.scrollLeft;}}},hide:function(){this.frame.style.display='none';},CLASS_NAME:"OpenLayers.Tile.Image"});OpenLayers.Tile.Image.useBlankTile=(OpenLayers.Util.getBrowserName()=="safari"||OpenLayers.Util.getBrowserName()=="opera");OpenLayers.Control.OverviewMap=OpenLayers.Class(OpenLayers.Control,{element:null,ovmap:null,size:new OpenLayers.Size(180,90),layers:null,minRectSize:15,minRectDisplayClass:"RectReplacement",minRatio:8,maxRatio:32,mapOptions:null,handlers:null,resolutionFactor:1,initialize:function(options){this.layers=[];this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,[options]);},destroy:function(){if(!this.mapDiv){return;}
 this.handlers.click.destroy();this.mapDiv.removeChild(this.extentRectangle);this.extentRectangle=null;this.rectEvents.destroy();this.rectEvents=null;this.ovmap.destroy();this.ovmap=null;this.element.removeChild(this.mapDiv);this.mapDiv=null;this.div.removeChild(this.element);this.element=null;if(this.maximizeDiv){OpenLayers.Event.stopObservingElement(this.maximizeDiv);this.div.removeChild(this.maximizeDiv);this.maximizeDiv=null;}
 if(this.minimizeDiv){OpenLayers.Event.stopObservingElement(this.minimizeDiv);this.div.removeChild(this.minimizeDiv);this.minimizeDiv=null;}
 this.map.events.un({"moveend":this.update,"changebaselayer":this.baseLayerDraw,scope:this});OpenLayers.Control.prototype.destroy.apply(this,arguments);},draw:function(){OpenLayers.Control.prototype.draw.apply(this,arguments);if(!(this.layers.length>0)){if(this.map.baseLayer){var layer=this.map.baseLayer.clone();this.layers=[layer];}else{this.map.events.register("changebaselayer",this,this.baseLayerDraw);return this.div;}}
-this.element=document.createElement('div');this.element.className=this.displayClass+'Element';this.element.style.display='none';this.mapDiv=document.createElement('div');this.mapDiv.style.width=this.size.w+'px';this.mapDiv.style.height=this.size.h+'px';this.mapDiv.style.position='relative';this.mapDiv.style.overflow='hidden';this.mapDiv.id=OpenLayers.Util.createUniqueID('overviewMap');this.extentRectangle=document.createElement('div');this.extentRectangle.style.position='absolute';this.extentRectangle.style.zIndex=1000;this.extentRectangle.className=this.displayClass+'ExtentRectangle';this.mapDiv.appendChild(this.extentRectangle);this.element.appendChild(this.mapDiv);this.div.appendChild(this.element);if(!this.outsideViewport){this.div.className+=" "+this.displayClass+'Container';var imgLocation=OpenLayers.Util.getImagesLocation();var img=imgLocation+'layer-switcher-maximize.png';this.maximizeDiv=OpenLayers.Util.createAlphaImageDiv(this.displayClass+'MaximizeButton',null,new OpenLayers.Size(18,18),img,'absolute');this.maximizeDiv.style.display='none';this.maximizeDiv.className=this.displayClass+'MaximizeButton';OpenLayers.Event.observe(this.maximizeDiv,'click',OpenLayers.Function.bindAsEventListener(this.maximizeControl,this));this.div.appendChild(this.maximizeDiv);var img=imgLocation+'layer-switcher-minimize.png';this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv('OpenLayers_Control_minimizeDiv',null,new OpenLayers.Size(18,18),img,'absolute');this.minimizeDiv.style.display='none';this.minimizeDiv.className=this.displayClass+'MinimizeButton';OpenLayers.Event.observe(this.minimizeDiv,'click',OpenLayers.Function.bindAsEventListener(this.minimizeControl,this));this.div.appendChild(this.minimizeDiv);var eventsToStop=['dblclick','mousedown'];for(var i=0;i<eventsToStop.length;i++){OpenLayers.Event.observe(this.maximizeDiv,eventsToStop[i],OpenLayers.Event.stop);OpenLayers.Event.observe(this.minimizeDiv,eventsToStop[i],OpenLayers.Event.stop);}
+this.element=document.createElement('div');this.element.className=this.displayClass+'Element';this.element.style.display='none';this.mapDiv=document.createElement('div');this.mapDiv.style.width=this.size.w+'px';this.mapDiv.style.height=this.size.h+'px';this.mapDiv.style.position='relative';this.mapDiv.style.overflow='hidden';this.mapDiv.id=OpenLayers.Util.createUniqueID('overviewMap');this.extentRectangle=document.createElement('div');this.extentRectangle.style.position='absolute';this.extentRectangle.style.zIndex=1000;this.extentRectangle.className=this.displayClass+'ExtentRectangle';this.mapDiv.appendChild(this.extentRectangle);this.element.appendChild(this.mapDiv);this.div.appendChild(this.element);if(!this.outsideViewport){this.div.className+=" "+this.displayClass+'Container';var imgLocation=OpenLayers.Util.getImagesLocation();var img=imgLocation+'layer-switcher-maximize.png';this.maximizeDiv=OpenLayers.Util.createAlphaImageDiv(this.displayClass+'MaximizeButton',null,new OpenLayers.Size(18,18),img,'absolute');this.maximizeDiv.style.display='none';this.maximizeDiv.className=this.displayClass+'MaximizeButton';OpenLayers.Event.observe(this.maximizeDiv,'click',OpenLayers.Function.bindAsEventListener(this.maximizeControl,this));this.div.appendChild(this.maximizeDiv);var img=imgLocation+'layer-switcher-minimize.png';this.minimizeDiv=OpenLayers.Util.createAlphaImageDiv('OpenLayers_Control_minimizeDiv',null,new OpenLayers.Size(18,18),img,'absolute');this.minimizeDiv.style.display='none';this.minimizeDiv.className=this.displayClass+'MinimizeButton';OpenLayers.Event.observe(this.minimizeDiv,'click',OpenLayers.Function.bindAsEventListener(this.minimizeControl,this));this.div.appendChild(this.minimizeDiv);var eventsToStop=['dblclick','mousedown'];for(var i=0,len=eventsToStop.length;i<len;i++){OpenLayers.Event.observe(this.maximizeDiv,eventsToStop[i],OpenLayers.Event.stop);OpenLayers.Event.observe(this.minimizeDiv,eventsToStop[i],OpenLayers.Event.stop);}
 this.minimizeControl();}else{this.element.style.display='';}
 if(this.map.getExtent()){this.update();}
 this.map.events.register('moveend',this,this.update);return this.div;},baseLayerDraw:function(){this.draw();this.map.events.unregister("changebaselayer",this,this.baseLayerDraw);},rectDrag:function(px){var deltaX=this.handlers.drag.last.x-px.x;var deltaY=this.handlers.drag.last.y-px.y;if(deltaX!=0||deltaY!=0){var rectTop=this.rectPxBounds.top;var rectLeft=this.rectPxBounds.left;var rectHeight=Math.abs(this.rectPxBounds.getHeight());var rectWidth=this.rectPxBounds.getWidth();var newTop=Math.max(0,(rectTop-deltaY));newTop=Math.min(newTop,this.ovmap.size.h-this.hComp-rectHeight);var newLeft=Math.max(0,(rectLeft-deltaX));newLeft=Math.min(newLeft,this.ovmap.size.w-this.wComp-rectWidth);this.setRectPxBounds(new OpenLayers.Bounds(newLeft,newTop+rectHeight,newLeft+rectWidth,newTop));}},mapDivClick:function(evt){var pxCenter=this.rectPxBounds.getCenterPixel();var deltaX=evt.xy.x-pxCenter.x;var deltaY=evt.xy.y-pxCenter.y;var top=this.rectPxBounds.top;var left=this.rectPxBounds.left;var height=Math.abs(this.rectPxBounds.getHeight());var width=this.rectPxBounds.getWidth();var newTop=Math.max(0,(top+deltaY));newTop=Math.min(newTop,this.ovmap.size.h-height);var newLeft=Math.max(0,(left+deltaX));newLeft=Math.min(newLeft,this.ovmap.size.w-width);this.setRectPxBounds(new OpenLayers.Bounds(newLeft,newTop+height,newLeft+width,newTop));this.updateMapToRect();},maximizeControl:function(e){this.element.style.display='';this.showToggle(false);if(e!=null){OpenLayers.Event.stop(e);}},minimizeControl:function(e){this.element.style.display='none';this.showToggle(true);if(e!=null){OpenLayers.Event.stop(e);}},showToggle:function(minimize){this.maximizeDiv.style.display=minimize?'':'none';this.minimizeDiv.style.display=minimize?'none':'';},update:function(){if(this.ovmap==null){this.createMap();}
 if(!this.isSuitableOverview()){this.updateOverview();}
-this.updateRectToMap();},isSuitableOverview:function(){var mapExtent=this.map.getExtent();var maxExtent=this.map.maxExtent;var testExtent=new OpenLayers.Bounds(Math.max(mapExtent.left,maxExtent.left),Math.max(mapExtent.bottom,maxExtent.bottom),Math.min(mapExtent.right,maxExtent.right),Math.min(mapExtent.top,maxExtent.top));var resRatio=this.ovmap.getResolution()/this.map.getResolution();return((resRatio>this.minRatio)&&(resRatio<=this.maxRatio)&&(this.ovmap.getExtent().containsBounds(testExtent)));},updateOverview:function(){var mapRes=this.map.getResolution();var targetRes=this.ovmap.getResolution();var resRatio=targetRes/mapRes;if(resRatio>this.maxRatio){targetRes=this.minRatio*mapRes;}else if(resRatio<=this.minRatio){targetRes=this.maxRatio*mapRes;}
-this.ovmap.setCenter(this.map.center,this.ovmap.getZoomForResolution(targetRes));this.updateRectToMap();},createMap:function(){var options=OpenLayers.Util.extend({controls:[],maxResolution:'auto',fallThrough:false},this.mapOptions);this.ovmap=new OpenLayers.Map(this.mapDiv,options);OpenLayers.Event.stopObserving(window,'unload',this.ovmap.unloadDestroy);this.ovmap.addLayers(this.layers);this.ovmap.zoomToMaxExtent();this.wComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-left-width'))+
+this.updateRectToMap();},isSuitableOverview:function(){var mapExtent=this.map.getExtent();var maxExtent=this.map.maxExtent;var testExtent=new OpenLayers.Bounds(Math.max(mapExtent.left,maxExtent.left),Math.max(mapExtent.bottom,maxExtent.bottom),Math.min(mapExtent.right,maxExtent.right),Math.min(mapExtent.top,maxExtent.top));if(this.ovmap.getProjection()!=this.map.getProjection()){testExtent=testExtent.transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}
+var resRatio=this.ovmap.getResolution()/this.map.getResolution();return((resRatio>this.minRatio)&&(resRatio<=this.maxRatio)&&(this.ovmap.getExtent().containsBounds(testExtent)));},updateOverview:function(){var mapRes=this.map.getResolution();var targetRes=this.ovmap.getResolution();var resRatio=targetRes/mapRes;if(resRatio>this.maxRatio){targetRes=this.minRatio*mapRes;}else if(resRatio<=this.minRatio){targetRes=this.maxRatio*mapRes;}
+var center;if(this.ovmap.getProjection()!=this.map.getProjection()){center=this.map.center.clone();center.transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}else{center=this.map.center;}
+this.ovmap.setCenter(center,this.ovmap.getZoomForResolution(targetRes*this.resolutionFactor));this.updateRectToMap();},createMap:function(){var options=OpenLayers.Util.extend({controls:[],maxResolution:'auto',fallThrough:false},this.mapOptions);this.ovmap=new OpenLayers.Map(this.mapDiv,options);OpenLayers.Event.stopObserving(window,'unload',this.ovmap.unloadDestroy);this.ovmap.addLayers(this.layers);this.ovmap.zoomToMaxExtent();this.wComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-left-width'))+
 parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-right-width'));this.wComp=(this.wComp)?this.wComp:2;this.hComp=parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-top-width'))+
-parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-bottom-width'));this.hComp=(this.hComp)?this.hComp:2;this.handlers.drag=new OpenLayers.Handler.Drag(this,{move:this.rectDrag,done:this.updateMapToRect},{map:this.ovmap});this.handlers.click=new OpenLayers.Handler.Click(this,{"click":this.mapDivClick},{"single":true,"double":false,"stopSingle":true,"stopDouble":true,"pixelTolerance":1,map:this.ovmap});this.handlers.click.activate();this.rectEvents=new OpenLayers.Events(this,this.extentRectangle,null,true);this.rectEvents.register("mouseover",this,function(e){if(!this.handlers.drag.active&&!this.map.dragging){this.handlers.drag.activate();}});this.rectEvents.register("mouseout",this,function(e){if(!this.handlers.drag.dragging){this.handlers.drag.deactivate();}});},updateRectToMap:function(){if(this.map.units!='degrees'){if(this.ovmap.getProjection()&&(this.map.getProjection()!=this.ovmap.getProjection())){alert(OpenLayers.i18n("sameProjection"));}}
-var pxBounds=this.getRectBoundsFromMapBounds(this.map.getExtent());if(pxBounds){this.setRectPxBounds(pxBounds);}},updateMapToRect:function(){var lonLatBounds=this.getMapBoundsFromRectBounds(this.rectPxBounds);this.map.panTo(lonLatBounds.getCenterLonLat());},setRectPxBounds:function(pxBounds){var top=Math.max(pxBounds.top,0);var left=Math.max(pxBounds.left,0);var bottom=Math.min(pxBounds.top+Math.abs(pxBounds.getHeight()),this.ovmap.size.h-this.hComp);var right=Math.min(pxBounds.left+pxBounds.getWidth(),this.ovmap.size.w-this.wComp);var width=Math.max(right-left,0);var height=Math.max(bottom-top,0);if(width<this.minRectSize||height<this.minRectSize){this.extentRectangle.className=this.displayClass+
+parseInt(OpenLayers.Element.getStyle(this.extentRectangle,'border-bottom-width'));this.hComp=(this.hComp)?this.hComp:2;this.handlers.drag=new OpenLayers.Handler.Drag(this,{move:this.rectDrag,done:this.updateMapToRect},{map:this.ovmap});this.handlers.click=new OpenLayers.Handler.Click(this,{"click":this.mapDivClick},{"single":true,"double":false,"stopSingle":true,"stopDouble":true,"pixelTolerance":1,map:this.ovmap});this.handlers.click.activate();this.rectEvents=new OpenLayers.Events(this,this.extentRectangle,null,true);this.rectEvents.register("mouseover",this,function(e){if(!this.handlers.drag.active&&!this.map.dragging){this.handlers.drag.activate();}});this.rectEvents.register("mouseout",this,function(e){if(!this.handlers.drag.dragging){this.handlers.drag.deactivate();}});if(this.ovmap.getProjection()!=this.map.getProjection()){var sourceUnits=this.map.getProjectionObject().getUnits()||this.map.units||this.map.baseLayer.units;var targetUnits=this.ovmap.getProjectionObject().getUnits()||this.ovmap.units||this.ovmap.baseLayer.units;this.resolutionFactor=sourceUnits&&targetUnits?OpenLayers.INCHES_PER_UNIT[sourceUnits]/OpenLayers.INCHES_PER_UNIT[targetUnits]:1;}},updateRectToMap:function(){var bounds;if(this.ovmap.getProjection()!=this.map.getProjection()){bounds=this.map.getExtent().transform(this.map.getProjectionObject(),this.ovmap.getProjectionObject());}else{bounds=this.map.getExtent();}
+var pxBounds=this.getRectBoundsFromMapBounds(bounds);if(pxBounds){this.setRectPxBounds(pxBounds);}},updateMapToRect:function(){var lonLatBounds=this.getMapBoundsFromRectBounds(this.rectPxBounds);if(this.ovmap.getProjection()!=this.map.getProjection()){lonLatBounds=lonLatBounds.transform(this.ovmap.getProjectionObject(),this.map.getProjectionObject());}
+this.map.panTo(lonLatBounds.getCenterLonLat());},setRectPxBounds:function(pxBounds){var top=Math.max(pxBounds.top,0);var left=Math.max(pxBounds.left,0);var bottom=Math.min(pxBounds.top+Math.abs(pxBounds.getHeight()),this.ovmap.size.h-this.hComp);var right=Math.min(pxBounds.left+pxBounds.getWidth(),this.ovmap.size.w-this.wComp);var width=Math.max(right-left,0);var height=Math.max(bottom-top,0);if(width<this.minRectSize||height<this.minRectSize){this.extentRectangle.className=this.displayClass+
 this.minRectDisplayClass;var rLeft=left+(width/2)-(this.minRectSize/2);var rTop=top+(height/2)-(this.minRectSize/2);this.extentRectangle.style.top=Math.round(rTop)+'px';this.extentRectangle.style.left=Math.round(rLeft)+'px';this.extentRectangle.style.height=this.minRectSize+'px';this.extentRectangle.style.width=this.minRectSize+'px';}else{this.extentRectangle.className=this.displayClass+'ExtentRectangle';this.extentRectangle.style.top=Math.round(top)+'px';this.extentRectangle.style.left=Math.round(left)+'px';this.extentRectangle.style.height=Math.round(height)+'px';this.extentRectangle.style.width=Math.round(width)+'px';}
 this.rectPxBounds=new OpenLayers.Bounds(Math.round(left),Math.round(bottom),Math.round(right),Math.round(top));},getRectBoundsFromMapBounds:function(lonLatBounds){var leftBottomLonLat=new OpenLayers.LonLat(lonLatBounds.left,lonLatBounds.bottom);var rightTopLonLat=new OpenLayers.LonLat(lonLatBounds.right,lonLatBounds.top);var leftBottomPx=this.getOverviewPxFromLonLat(leftBottomLonLat);var rightTopPx=this.getOverviewPxFromLonLat(rightTopLonLat);var bounds=null;if(leftBottomPx&&rightTopPx){bounds=new OpenLayers.Bounds(leftBottomPx.x,leftBottomPx.y,rightTopPx.x,rightTopPx.y);}
 return bounds;},getMapBoundsFromRectBounds:function(pxBounds){var leftBottomPx=new OpenLayers.Pixel(pxBounds.left,pxBounds.bottom);var rightTopPx=new OpenLayers.Pixel(pxBounds.right,pxBounds.top);var leftBottomLonLat=this.getLonLatFromOverviewPx(leftBottomPx);var rightTopLonLat=this.getLonLatFromOverviewPx(rightTopPx);return new OpenLayers.Bounds(leftBottomLonLat.lon,leftBottomLonLat.lat,rightTopLonLat.lon,rightTopLonLat.lat);},getLonLatFromOverviewPx:function(overviewMapPx){var size=this.ovmap.size;var res=this.ovmap.getResolution();var center=this.ovmap.getExtent().getCenterLonLat();var delta_x=overviewMapPx.x-(size.w/2);var delta_y=overviewMapPx.y-(size.h/2);return new OpenLayers.LonLat(center.lon+delta_x*res,center.lat-delta_y*res);},getOverviewPxFromLonLat:function(lonlat){var res=this.ovmap.getResolution();var extent=this.ovmap.getExtent();var px=null;if(extent){px=new OpenLayers.Pixel(Math.round(1/res*(lonlat.lon-extent.left)),Math.round(1/res*(extent.top-lonlat.lat)));}
-return px;},CLASS_NAME:'OpenLayers.Control.OverviewMap'});OpenLayers.Handler.Click=OpenLayers.Class(OpenLayers.Handler,{delay:300,single:true,'double':false,pixelTolerance:0,stopSingle:false,stopDouble:false,timerId:null,down:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);if(this.pixelTolerance!=null){this.mousedown=function(evt){this.down=evt.xy;return true;};}},mousedown:null,dblclick:function(evt){if(this.passesTolerance(evt)){if(this["double"]){this.callback('dblclick',[evt]);}
+return px;},CLASS_NAME:'OpenLayers.Control.OverviewMap'});OpenLayers.Feature=OpenLayers.Class({layer:null,id:null,lonlat:null,data:null,marker:null,popupClass:OpenLayers.Popup.AnchoredBubble,popup:null,initialize:function(layer,lonlat,data){this.layer=layer;this.lonlat=lonlat;this.data=(data!=null)?data:{};this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){if((this.layer!=null)&&(this.layer.map!=null)){if(this.popup!=null){this.layer.map.removePopup(this.popup);}}
+this.layer=null;this.id=null;this.lonlat=null;this.data=null;if(this.marker!=null){this.destroyMarker(this.marker);this.marker=null;}
+if(this.popup!=null){this.destroyPopup(this.popup);this.popup=null;}},onScreen:function(){var onScreen=false;if((this.layer!=null)&&(this.layer.map!=null)){var screenBounds=this.layer.map.getExtent();onScreen=screenBounds.containsLonLat(this.lonlat);}
+return onScreen;},createMarker:function(){if(this.lonlat!=null){this.marker=new OpenLayers.Marker(this.lonlat,this.data.icon);}
+return this.marker;},destroyMarker:function(){this.marker.destroy();},createPopup:function(closeBox){if(this.lonlat!=null){var id=this.id+"_popup";var anchor=(this.marker)?this.marker.icon:null;if(!this.popup){this.popup=new this.popupClass(id,this.lonlat,this.data.popupSize,this.data.popupContentHTML,anchor,closeBox);}
+if(this.data.overflow!=null){this.popup.contentDiv.style.overflow=this.data.overflow;}
+this.popup.feature=this;}
+return this.popup;},destroyPopup:function(){if(this.popup){this.popup.feature=null;this.popup.destroy();this.popup=null;}},CLASS_NAME:"OpenLayers.Feature"});OpenLayers.Handler.Click=OpenLayers.Class(OpenLayers.Handler,{delay:300,single:true,'double':false,pixelTolerance:0,stopSingle:false,stopDouble:false,timerId:null,down:null,rightclickTimerId:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);if(this.pixelTolerance!=null){this.mousedown=function(evt){this.down=evt.xy;return true;};}},mousedown:null,mouseup:function(evt){var propagate=true;if(this.checkModifiers(evt)&&this.control.handleRightClicks&&OpenLayers.Event.isRightClick(evt)){propogate=this.rightclick(evt);}
+return propagate;},rightclick:function(evt){if(this.passesTolerance(evt)){if(this.rightclickTimerId!=null){this.clearTimer();this.callback('dblrightclick',[evt]);return!this.stopDouble;}else{var clickEvent=this['double']?OpenLayers.Util.extend({},evt):this.callback('rightclick',[evt]);var delayedRightCall=OpenLayers.Function.bind(this.delayedRightCall,this,clickEvent);this.rightclickTimerId=window.setTimeout(delayedRightCall,this.delay);}}
+return!this.stopSingle;},delayedRightCall:function(evt){this.rightclickTimerId=null;if(evt){this.callback('rightclick',[evt]);}
+return!this.stopSingle;},dblclick:function(evt){if(this.passesTolerance(evt)){if(this["double"]){this.callback('dblclick',[evt]);}
 this.clearTimer();}
 return!this.stopDouble;},click:function(evt){if(this.passesTolerance(evt)){if(this.timerId!=null){this.clearTimer();}else{var clickEvent=this.single?OpenLayers.Util.extend({},evt):null;this.timerId=window.setTimeout(OpenLayers.Function.bind(this.delayedCall,this,clickEvent),this.delay);}}
 return!this.stopSingle;},passesTolerance:function(evt){var passes=true;if(this.pixelTolerance!=null&&this.down){var dpx=Math.sqrt(Math.pow(this.down.x-evt.xy.x,2)+
 Math.pow(this.down.y-evt.xy.y,2));if(dpx>this.pixelTolerance){passes=false;}}
 return passes;},clearTimer:function(){if(this.timerId!=null){window.clearTimeout(this.timerId);this.timerId=null;}},delayedCall:function(evt){this.timerId=null;if(evt){this.callback('click',[evt]);}},deactivate:function(){var deactivated=false;if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.clearTimer();this.down=null;deactivated=true;}
-return deactivated;},CLASS_NAME:"OpenLayers.Handler.Click"});OpenLayers.Handler.Drag=OpenLayers.Class(OpenLayers.Handler,{started:false,stopDown:true,dragging:false,last:null,start:null,oldOnselectstart:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);},down:function(evt){},move:function(evt){},up:function(evt){},out:function(evt){},mousedown:function(evt){var propagate=true;this.dragging=false;if(this.checkModifiers(evt)&&OpenLayers.Event.isLeftClick(evt)){this.started=true;this.start=evt.xy;this.last=evt.xy;this.map.div.style.cursor="move";this.down(evt);this.callback("down",[evt.xy]);OpenLayers.Event.stop(evt);if(!this.oldOnselectstart){this.oldOnselectstart=(document.onselectstart)?document.onselectstart:function(){return true;};document.onselectstart=function(){return false;};}
+return deactivated;},CLASS_NAME:"OpenLayers.Handler.Click"});OpenLayers.Handler.Drag=OpenLayers.Class(OpenLayers.Handler,{started:false,stopDown:true,dragging:false,last:null,start:null,oldOnselectstart:null,interval:0,timeoutId:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);},down:function(evt){},move:function(evt){},up:function(evt){},out:function(evt){},mousedown:function(evt){var propagate=true;this.dragging=false;if(this.checkModifiers(evt)&&OpenLayers.Event.isLeftClick(evt)){this.started=true;this.start=evt.xy;this.last=evt.xy;this.map.div.style.cursor="move";this.down(evt);this.callback("down",[evt.xy]);OpenLayers.Event.stop(evt);if(!this.oldOnselectstart){this.oldOnselectstart=(document.onselectstart)?document.onselectstart:function(){return true;};document.onselectstart=function(){return false;};}
 propagate=!this.stopDown;}else{this.started=false;this.start=null;this.last=null;}
-return propagate;},mousemove:function(evt){if(this.started){if(evt.xy.x!=this.last.x||evt.xy.y!=this.last.y){this.dragging=true;this.move(evt);this.callback("move",[evt.xy]);if(!this.oldOnselectstart){this.oldOnselectstart=document.onselectstart;document.onselectstart=function(){return false;};}
-this.last=evt.xy;}}
-return true;},mouseup:function(evt){if(this.started){var dragged=(this.start!=this.last);this.started=false;this.dragging=false;this.map.div.style.cursor="";this.up(evt);this.callback("up",[evt.xy]);if(dragged){this.callback("done",[evt.xy]);}
+return propagate;},mousemove:function(evt){if(this.started&&!this.timeoutId&&(evt.xy.x!=this.last.x||evt.xy.y!=this.last.y)){if(this.interval>0){this.timeoutId=setTimeout(OpenLayers.Function.bind(this.removeTimeout,this),this.interval);}
+this.dragging=true;this.move(evt);this.callback("move",[evt.xy]);if(!this.oldOnselectstart){this.oldOnselectstart=document.onselectstart;document.onselectstart=function(){return false;};}
+this.last=this.evt.xy;}
+return true;},removeTimeout:function(){this.timeoutId=null;},mouseup:function(evt){if(this.started){var dragged=(this.start!=this.last);this.started=false;this.dragging=false;this.map.div.style.cursor="";this.up(evt);this.callback("up",[evt.xy]);if(dragged){this.callback("done",[evt.xy]);}
 document.onselectstart=this.oldOnselectstart;}
 return true;},mouseout:function(evt){if(this.started&&OpenLayers.Util.mouseLeft(evt,this.map.div)){var dragged=(this.start!=this.last);this.started=false;this.dragging=false;this.map.div.style.cursor="";this.out(evt);this.callback("out",[]);if(dragged){this.callback("done",[evt.xy]);}
 if(document.onselectstart){document.onselectstart=this.oldOnselectstart;}}
@@ -434,14 +535,14 @@
 return deactivated;},CLASS_NAME:"OpenLayers.Handler.Drag"});OpenLayers.Handler.MouseWheel=OpenLayers.Class(OpenLayers.Handler,{wheelListener:null,mousePosition:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);this.wheelListener=OpenLayers.Function.bindAsEventListener(this.onWheelEvent,this);},destroy:function(){OpenLayers.Handler.prototype.destroy.apply(this,arguments);this.wheelListener=null;},onWheelEvent:function(e){if(!this.map||!this.checkModifiers(e)){return;}
 var overScrollableDiv=false;var overLayerDiv=false;var overMapDiv=false;var elem=OpenLayers.Event.element(e);while((elem!=null)&&!overMapDiv&&!overScrollableDiv){if(!overScrollableDiv){try{if(elem.currentStyle){overflow=elem.currentStyle["overflow"];}else{var style=document.defaultView.getComputedStyle(elem,null);var overflow=style.getPropertyValue("overflow");}
 overScrollableDiv=(overflow&&(overflow=="auto")||(overflow=="scroll"));}catch(err){}}
-if(!overLayerDiv){for(var i=0;i<this.map.layers.length;i++){if(elem==this.map.layers[i].div||elem==this.map.layers[i].pane){overLayerDiv=true;break;}}}
+if(!overLayerDiv){for(var i=0,len=this.map.layers.length;i<len;i++){if(elem==this.map.layers[i].div||elem==this.map.layers[i].pane){overLayerDiv=true;break;}}}
 overMapDiv=(elem==this.map.div);elem=elem.parentNode;}
 if(!overScrollableDiv&&overMapDiv){if(overLayerDiv){this.wheelZoom(e);}
 OpenLayers.Event.stop(e);}},wheelZoom:function(e){var delta=0;if(!e){e=window.event;}
 if(e.wheelDelta){delta=e.wheelDelta/120;if(window.opera&&window.opera.version()<9.2){delta=-delta;}}else if(e.detail){delta=-e.detail/3;}
 if(delta){if(this.mousePosition){e.xy=this.mousePosition;}
 if(!e.xy){e.xy=this.map.getPixelFromLonLat(this.map.getCenter());}
-if(delta<0){this.callback("down",[e,delta]);}else{this.callback("up",[e,delta]);}}},mousemove:function(evt){this.mousePosition=evt.xy;},activate:function(evt){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){var wheelListener=this.wheelListener;OpenLayers.Event.observe(window,"DOMMouseScroll",wheelListener);OpenLayers.Event.observe(window,"mousewheel",wheelListener);OpenLayers.Event.observe(document,"mousewheel",wheelListener);return true;}else{return false;}},deactivate:function(evt){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){var wheelListener=this.wheelListener;OpenLayers.Event.stopObserving(window,"DOMMouseScroll",wheelListener);OpenLayers.Event.stopObserving(window,"mousewheel",wheelListener);OpenLayers.Event.stopObserving(document,"mousewheel",wheelListener);return true;}else{return false;}},CLASS_NAME:"OpenLayers.Handler.MouseWheel"});OpenLayers.Layer=OpenLayers.Class({id:null,name:null,div:null,opacity:null,EVENT_TYPES:["loadstart","loadend","loadcancel","visibilitychanged"],events:null,map:null,isBaseLayer:false,alpha:false,displayInLayerSwitcher:true,visibility:true,attribution:null,inRange:false,imageSize:null,imageOffset:null,options:null,eventListeners:null,gutter:0,projection:null,units:null,scales:null,resolutions:null,maxExtent:null,minExtent:null,maxResolution:null,minResolution:null,numZoomLevels:null,minScale:null,maxScale:null,displayOutsideMaxExtent:false,wrapDateLine:false,transitionEffect:null,SUPPORTED_TRANSITIONS:['resize'],initialize:function(name,options){this.addOptions(options);this.name=name;if(this.id==null){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");this.div=OpenLayers.Util.createDiv(this.id);this.div.style.width="100%";this.div.style.height="100%";this.events=new OpenLayers.Events(this,this.div,this.EVENT_TYPES);if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}}
+if(delta<0){this.callback("down",[e,delta]);}else{this.callback("up",[e,delta]);}}},mousemove:function(evt){this.mousePosition=evt.xy;},activate:function(evt){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){var wheelListener=this.wheelListener;OpenLayers.Event.observe(window,"DOMMouseScroll",wheelListener);OpenLayers.Event.observe(window,"mousewheel",wheelListener);OpenLayers.Event.observe(document,"mousewheel",wheelListener);return true;}else{return false;}},deactivate:function(evt){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){var wheelListener=this.wheelListener;OpenLayers.Event.stopObserving(window,"DOMMouseScroll",wheelListener);OpenLayers.Event.stopObserving(window,"mousewheel",wheelListener);OpenLayers.Event.stopObserving(document,"mousewheel",wheelListener);return true;}else{return false;}},CLASS_NAME:"OpenLayers.Handler.MouseWheel"});OpenLayers.Layer=OpenLayers.Class({id:null,name:null,div:null,opacity:null,alwaysInRange:null,EVENT_TYPES:["loadstart","loadend","loadcancel","visibilitychanged","moveend"],events:null,map:null,isBaseLayer:false,alpha:false,displayInLayerSwitcher:true,visibility:true,attribution:null,inRange:false,imageSize:null,imageOffset:null,options:null,eventListeners:null,gutter:0,projection:null,units:null,scales:null,resolutions:null,maxExtent:null,minExtent:null,maxResolution:null,minResolution:null,numZoomLevels:null,minScale:null,maxScale:null,displayOutsideMaxExtent:false,wrapDateLine:false,transitionEffect:null,SUPPORTED_TRANSITIONS:['resize'],initialize:function(name,options){this.addOptions(options);this.name=name;if(this.id==null){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");this.div=OpenLayers.Util.createDiv(this.id);this.div.style.width="100%";this.div.style.height="100%";this.events=new OpenLayers.Events(this,this.div,this.EVENT_TYPES);if(this.eventListeners instanceof Object){this.events.on(this.eventListeners);}}
 if(this.wrapDateLine){this.displayOutsideMaxExtent=true;}},destroy:function(setNewBaseLayer){if(setNewBaseLayer==null){setNewBaseLayer=true;}
 if(this.map!=null){this.map.removeLayer(this,setNewBaseLayer);}
 this.projection=null;this.map=null;this.name=null;this.div=null;this.options=null;if(this.events){if(this.eventListeners){this.events.un(this.eventListeners);}
@@ -453,41 +554,73 @@
 this.display(display);},setMap:function(map){if(this.map==null){this.map=map;this.maxExtent=this.maxExtent||this.map.maxExtent;this.projection=this.projection||this.map.projection;if(this.projection&&typeof this.projection=="string"){this.projection=new OpenLayers.Projection(this.projection);}
 this.units=this.projection.getUnits()||this.units||this.map.units;this.initResolutions();if(!this.isBaseLayer){this.inRange=this.calculateInRange();var show=((this.visibility)&&(this.inRange));this.div.style.display=show?"":"none";}
 this.setTileSize();}},removeMap:function(map){},getImageSize:function(){return(this.imageSize||this.tileSize);},setTileSize:function(size){var tileSize=(size)?size:((this.tileSize)?this.tileSize:this.map.getTileSize());this.tileSize=tileSize;if(this.gutter){this.imageOffset=new OpenLayers.Pixel(-this.gutter,-this.gutter);this.imageSize=new OpenLayers.Size(tileSize.w+(2*this.gutter),tileSize.h+(2*this.gutter));}},getVisibility:function(){return this.visibility;},setVisibility:function(visibility){if(visibility!=this.visibility){this.visibility=visibility;this.display(visibility);this.redraw();if(this.map!=null){this.map.events.triggerEvent("changelayer",{layer:this,property:"visibility"});}
-this.events.triggerEvent("visibilitychanged");}},display:function(display){var inRange=this.calculateInRange();if(display!=(this.div.style.display!="none")){this.div.style.display=(display&&inRange)?"block":"none";}},calculateInRange:function(){var inRange=false;if(this.map){var resolution=this.map.getResolution();inRange=((resolution>=this.minResolution)&&(resolution<=this.maxResolution));}
-return inRange;},setIsBaseLayer:function(isBaseLayer){if(isBaseLayer!=this.isBaseLayer){this.isBaseLayer=isBaseLayer;if(this.map!=null){this.map.events.triggerEvent("changebaselayer",{layer:this});}}},initResolutions:function(){var props=new Array('projection','units','scales','resolutions','maxScale','minScale','maxResolution','minResolution','minExtent','maxExtent','numZoomLevels','maxZoomLevel');var confProps={};for(var i=0;i<props.length;i++){var property=props[i];confProps[property]=this.options[property]||this.map[property];}
-if(this.options.minScale!=null&&this.options.maxScale!=null&&this.options.scales==null){confProps.scales=null;}
-if(this.options.minResolution!=null&&this.options.maxResolution!=null&&this.options.resolutions==null){confProps.resolutions=null;}
+this.events.triggerEvent("visibilitychanged");}},display:function(display){var inRange=this.calculateInRange();if(display!=(this.div.style.display!="none")){this.div.style.display=(display&&inRange)?"block":"none";}},calculateInRange:function(){var inRange=false;if(this.alwaysInRange){inRange=true;}else{if(this.map){var resolution=this.map.getResolution();inRange=((resolution>=this.minResolution)&&(resolution<=this.maxResolution));}}
+return inRange;},setIsBaseLayer:function(isBaseLayer){if(isBaseLayer!=this.isBaseLayer){this.isBaseLayer=isBaseLayer;if(this.map!=null){this.map.events.triggerEvent("changebaselayer",{layer:this});}}},initResolutions:function(){var props=new Array('projection','units','scales','resolutions','maxScale','minScale','maxResolution','minResolution','minExtent','maxExtent','numZoomLevels','maxZoomLevel');var notScaleProps=['projection','units'];var useInRange=false;var confProps={};for(var i=0,len=props.length;i<len;i++){var property=props[i];if(this.options[property]&&OpenLayers.Util.indexOf(notScaleProps,property)==-1){useInRange=true;}
+confProps[property]=this.options[property]||this.map[property];}
+if(this.alwaysInRange==null){this.alwaysInRange=!useInRange;}
+if((this.options.minScale!=null||this.options.maxScale!=null)&&this.options.scales==null){confProps.scales=null;}
+if((this.options.minResolution!=null||this.options.maxResolution!=null)&&this.options.resolutions==null){confProps.resolutions=null;}
 if((!confProps.numZoomLevels)&&(confProps.maxZoomLevel)){confProps.numZoomLevels=confProps.maxZoomLevel+1;}
-if((confProps.scales!=null)||(confProps.resolutions!=null)){if(confProps.scales!=null){confProps.resolutions=[];for(var i=0;i<confProps.scales.length;i++){var scale=confProps.scales[i];confProps.resolutions[i]=OpenLayers.Util.getResolutionFromScale(scale,confProps.units);}}
+if((confProps.scales!=null)||(confProps.resolutions!=null)){if(confProps.scales!=null){confProps.resolutions=[];for(var i=0,len=confProps.scales.length;i<len;i++){var scale=confProps.scales[i];confProps.resolutions[i]=OpenLayers.Util.getResolutionFromScale(scale,confProps.units);}}
 confProps.numZoomLevels=confProps.resolutions.length;}else{if(confProps.minScale){confProps.maxResolution=OpenLayers.Util.getResolutionFromScale(confProps.minScale,confProps.units);}else if(confProps.maxResolution=="auto"){var viewSize=this.map.getSize();var wRes=confProps.maxExtent.getWidth()/viewSize.w;var hRes=confProps.maxExtent.getHeight()/viewSize.h;confProps.maxResolution=Math.max(wRes,hRes);}
 if(confProps.maxScale!=null){confProps.minResolution=OpenLayers.Util.getResolutionFromScale(confProps.maxScale,confProps.units);}else if((confProps.minResolution=="auto")&&(confProps.minExtent!=null)){var viewSize=this.map.getSize();var wRes=confProps.minExtent.getWidth()/viewSize.w;var hRes=confProps.minExtent.getHeight()/viewSize.h;confProps.minResolution=Math.max(wRes,hRes);}
 if(confProps.minResolution!=null&&this.options.numZoomLevels==undefined){var ratio=confProps.maxResolution/confProps.minResolution;confProps.numZoomLevels=Math.floor(Math.log(ratio)/Math.log(2))+1;}
 confProps.resolutions=new Array(confProps.numZoomLevels);var base=2;if(typeof confProps.minResolution=="number"&&confProps.numZoomLevels>1){base=Math.pow((confProps.maxResolution/confProps.minResolution),(1/(confProps.numZoomLevels-1)));}
 for(var i=0;i<confProps.numZoomLevels;i++){var res=confProps.maxResolution/Math.pow(base,i);confProps.resolutions[i]=res;}}
-confProps.resolutions.sort(function(a,b){return(b-a);});this.resolutions=confProps.resolutions;this.maxResolution=confProps.resolutions[0];var lastIndex=confProps.resolutions.length-1;this.minResolution=confProps.resolutions[lastIndex];this.scales=[];for(var i=0;i<confProps.resolutions.length;i++){this.scales[i]=OpenLayers.Util.getScaleFromResolution(confProps.resolutions[i],confProps.units);}
+confProps.resolutions.sort(function(a,b){return(b-a);});this.resolutions=confProps.resolutions;this.maxResolution=confProps.resolutions[0];var lastIndex=confProps.resolutions.length-1;this.minResolution=confProps.resolutions[lastIndex];this.scales=[];for(var i=0,len=confProps.resolutions.length;i<len;i++){this.scales[i]=OpenLayers.Util.getScaleFromResolution(confProps.resolutions[i],confProps.units);}
 this.minScale=this.scales[0];this.maxScale=this.scales[this.scales.length-1];this.numZoomLevels=confProps.numZoomLevels;},getResolution:function(){var zoom=this.map.getZoom();return this.getResolutionForZoom(zoom);},getExtent:function(){return this.map.calculateBounds();},getZoomForExtent:function(extent,closest){var viewSize=this.map.getSize();var idealResolution=Math.max(extent.getWidth()/viewSize.w,extent.getHeight()/viewSize.h);return this.getZoomForResolution(idealResolution,closest);},getDataExtent:function(){},getResolutionForZoom:function(zoom){zoom=Math.max(0,Math.min(zoom,this.resolutions.length-1));var resolution;if(this.map.fractionalZoom){var low=Math.floor(zoom);var high=Math.ceil(zoom);resolution=this.resolutions[high]+
 ((zoom-low)*(this.resolutions[low]-this.resolutions[high]));}else{resolution=this.resolutions[Math.round(zoom)];}
-return resolution;},getZoomForResolution:function(resolution,closest){var zoom;if(this.map.fractionalZoom){var lowZoom=0;var highZoom=this.resolutions.length-1;var highRes=this.resolutions[lowZoom];var lowRes=this.resolutions[highZoom];var res;for(var i=0;i<this.resolutions.length;++i){res=this.resolutions[i];if(res>=resolution){highRes=res;lowZoom=i;}
+return resolution;},getZoomForResolution:function(resolution,closest){var zoom;if(this.map.fractionalZoom){var lowZoom=0;var highZoom=this.resolutions.length-1;var highRes=this.resolutions[lowZoom];var lowRes=this.resolutions[highZoom];var res;for(var i=0,len=this.resolutions.length;i<len;++i){res=this.resolutions[i];if(res>=resolution){highRes=res;lowZoom=i;}
 if(res<=resolution){lowRes=res;highZoom=i;break;}}
-var dRes=highRes-lowRes;if(dRes>0){zoom=lowZoom+((resolution-lowRes)/dRes);}else{zoom=lowZoom;}}else{var diff;var minDiff=Number.POSITIVE_INFINITY;for(var i=0;i<this.resolutions.length;i++){if(closest){diff=Math.abs(this.resolutions[i]-resolution);if(diff>minDiff){break;}
+var dRes=highRes-lowRes;if(dRes>0){zoom=lowZoom+((resolution-lowRes)/dRes);}else{zoom=lowZoom;}}else{var diff;var minDiff=Number.POSITIVE_INFINITY;for(var i=0,len=this.resolutions.length;i<len;i++){if(closest){diff=Math.abs(this.resolutions[i]-resolution);if(diff>minDiff){break;}
 minDiff=diff;}else{if(this.resolutions[i]<resolution){break;}}}
 zoom=Math.max(0,i-1);}
 return zoom;},getLonLatFromViewPortPx:function(viewPortPx){var lonlat=null;if(viewPortPx!=null){var size=this.map.getSize();var center=this.map.getCenter();if(center){var res=this.map.getResolution();var delta_x=viewPortPx.x-(size.w/2);var delta_y=viewPortPx.y-(size.h/2);lonlat=new OpenLayers.LonLat(center.lon+delta_x*res,center.lat-delta_y*res);if(this.wrapDateLine){lonlat=lonlat.wrapDateLine(this.maxExtent);}}}
 return lonlat;},getViewPortPxFromLonLat:function(lonlat){var px=null;if(lonlat!=null){var resolution=this.map.getResolution();var extent=this.map.getExtent();px=new OpenLayers.Pixel((1/resolution*(lonlat.lon-extent.left)),(1/resolution*(extent.top-lonlat.lat)));}
-return px;},setOpacity:function(opacity){if(opacity!=this.opacity){this.opacity=opacity;for(var i=0;i<this.div.childNodes.length;++i){var element=this.div.childNodes[i].firstChild;OpenLayers.Util.modifyDOMElement(element,null,null,null,null,null,null,opacity);}}},setZIndex:function(zIndex){this.div.style.zIndex=zIndex;},adjustBounds:function(bounds){if(this.gutter){var mapGutter=this.gutter*this.map.getResolution();bounds=new OpenLayers.Bounds(bounds.left-mapGutter,bounds.bottom-mapGutter,bounds.right+mapGutter,bounds.top+mapGutter);}
+return px;},setOpacity:function(opacity){if(opacity!=this.opacity){this.opacity=opacity;for(var i=0,len=this.div.childNodes.length;i<len;++i){var element=this.div.childNodes[i].firstChild;OpenLayers.Util.modifyDOMElement(element,null,null,null,null,null,null,opacity);}}},getZIndex:function(){return this.div.style.zIndex;},setZIndex:function(zIndex){this.div.style.zIndex=zIndex;},adjustBounds:function(bounds){if(this.gutter){var mapGutter=this.gutter*this.map.getResolution();bounds=new OpenLayers.Bounds(bounds.left-mapGutter,bounds.bottom-mapGutter,bounds.right+mapGutter,bounds.top+mapGutter);}
 if(this.wrapDateLine){var wrappingOptions={'rightTolerance':this.getResolution()};bounds=bounds.wrapDateLine(this.maxExtent,wrappingOptions);}
 return bounds;},CLASS_NAME:"OpenLayers.Layer"});OpenLayers.Marker.Box=OpenLayers.Class(OpenLayers.Marker,{bounds:null,div:null,initialize:function(bounds,borderColor,borderWidth){this.bounds=bounds;this.div=OpenLayers.Util.createDiv();this.div.style.overflow='hidden';this.events=new OpenLayers.Events(this,this.div,null);this.setBorder(borderColor,borderWidth);},destroy:function(){this.bounds=null;this.div=null;OpenLayers.Marker.prototype.destroy.apply(this,arguments);},setBorder:function(color,width){if(!color){color="red";}
 if(!width){width=2;}
 this.div.style.border=width+"px solid "+color;},draw:function(px,sz){OpenLayers.Util.modifyDOMElement(this.div,null,px,sz);return this.div;},onScreen:function(){var onScreen=false;if(this.map){var screenBounds=this.map.getExtent();onScreen=screenBounds.containsBounds(this.bounds,true,true);}
-return onScreen;},display:function(display){this.div.style.display=(display)?"":"none";},CLASS_NAME:"OpenLayers.Marker.Box"});OpenLayers.Control.DragPan=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,panned:false,draw:function(){this.handler=new OpenLayers.Handler.Drag(this,{"move":this.panMap,"done":this.panMapDone});},panMap:function(xy){this.panned=true;this.map.pan(this.handler.last.x-xy.x,this.handler.last.y-xy.y,{dragging:this.handler.dragging,animate:false});},panMapDone:function(xy){if(this.panned){this.panMap(xy);this.panned=false;}},CLASS_NAME:"OpenLayers.Control.DragPan"});OpenLayers.Handler.Box=OpenLayers.Class(OpenLayers.Handler,{dragHandler:null,boxDivClassName:'olHandlerBoxZoomBox',initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);var callbacks={"down":this.startBox,"move":this.moveBox,"out":this.removeBox,"up":this.endBox};this.dragHandler=new OpenLayers.Handler.Drag(this,callbacks,{keyMask:this.keyMask});},setMap:function(map){OpenLayers.Handler.prototype.setMap.apply(this,arguments);if(this.dragHandler){this.dragHandler.setMap(map);}},startBox:function(xy){this.zoomBox=OpenLayers.Util.createDiv('zoomBox',this.dragHandler.start);this.zoomBox.className=this.boxDivClassName;this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.viewPortDiv.appendChild(this.zoomBox);this.map.div.style.cursor="crosshair";},moveBox:function(xy){var deltaX=Math.abs(this.dragHandler.start.x-xy.x);var deltaY=Math.abs(this.dragHandler.start.y-xy.y);this.zoomBox.style.width=Math.max(1,deltaX)+"px";this.zoomBox.style.height=Math.max(1,deltaY)+"px";if(xy.x<this.dragHandler.start.x){this.zoomBox.style.left=xy.x+"px";}
-if(xy.y<this.dragHandler.start.y){this.zoomBox.style.top=xy.y+"px";}},endBox:function(end){var result;if(Math.abs(this.dragHandler.start.x-end.x)>5||Math.abs(this.dragHandler.start.y-end.y)>5){var start=this.dragHandler.start;var top=Math.min(start.y,end.y);var bottom=Math.max(start.y,end.y);var left=Math.min(start.x,end.x);var right=Math.max(start.x,end.x);result=new OpenLayers.Bounds(left,bottom,right,top);}else{result=this.dragHandler.start.clone();}
-this.removeBox();this.map.div.style.cursor="";this.callback("done",[result]);},removeBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.zoomBox=null;},activate:function(){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.dragHandler.activate();return true;}else{return false;}},deactivate:function(){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.dragHandler.deactivate();return true;}else{return false;}},CLASS_NAME:"OpenLayers.Handler.Box"});OpenLayers.Layer.HTTPRequest=OpenLayers.Class(OpenLayers.Layer,{URL_HASH_FACTOR:(Math.sqrt(5)-1)/2,url:null,params:null,reproject:false,initialize:function(name,url,params,options){var newArguments=arguments;newArguments=[name,options];OpenLayers.Layer.prototype.initialize.apply(this,newArguments);this.url=url;this.params=OpenLayers.Util.extend({},params);},destroy:function(){this.url=null;this.params=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.HTTPRequest(this.name,this.url,this.params,this.options);}
-obj=OpenLayers.Layer.prototype.clone.apply(this,[obj]);return obj;},setUrl:function(newUrl){this.url=newUrl;},mergeNewParams:function(newParams){this.params=OpenLayers.Util.extend(this.params,newParams);return this.redraw();},redraw:function(force){if(force){return this.mergeNewParams({"_olSalt":Math.random()});}else{return OpenLayers.Layer.prototype.redraw.apply(this,[]);}},selectUrl:function(paramString,urls){var product=1;for(var i=0;i<paramString.length;i++){product*=paramString.charCodeAt(i)*this.URL_HASH_FACTOR;product-=Math.floor(product);}
+return onScreen;},display:function(display){this.div.style.display=(display)?"":"none";},CLASS_NAME:"OpenLayers.Marker.Box"});OpenLayers.Control.DragPan=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,panned:false,interval:25,draw:function(){this.handler=new OpenLayers.Handler.Drag(this,{"move":this.panMap,"done":this.panMapDone},{interval:this.interval});},panMap:function(xy){this.panned=true;this.map.pan(this.handler.last.x-xy.x,this.handler.last.y-xy.y,{dragging:this.handler.dragging,animate:false});},panMapDone:function(xy){if(this.panned){this.panMap(xy);this.panned=false;}},CLASS_NAME:"OpenLayers.Control.DragPan"});OpenLayers.State={UNKNOWN:'Unknown',INSERT:'Insert',UPDATE:'Update',DELETE:'Delete'};OpenLayers.Feature.Vector=OpenLayers.Class(OpenLayers.Feature,{fid:null,geometry:null,attributes:null,state:null,style:null,renderIntent:"default",initialize:function(geometry,attributes,style){OpenLayers.Feature.prototype.initialize.apply(this,[null,null,attributes]);this.lonlat=null;this.geometry=geometry?geometry:null;this.state=null;this.attributes={};if(attributes){this.attributes=OpenLayers.Util.extend(this.attributes,attributes);}
+this.style=style?style:null;},destroy:function(){if(this.layer){this.layer.removeFeatures(this);this.layer=null;}
+this.geometry=null;OpenLayers.Feature.prototype.destroy.apply(this,arguments);},clone:function(){return new OpenLayers.Feature.Vector(this.geometry?this.geometry.clone():null,this.attributes,this.style);},onScreen:function(boundsOnly){var onScreen=false;if(this.layer&&this.layer.map){var screenBounds=this.layer.map.getExtent();if(boundsOnly){var featureBounds=this.geometry.getBounds();onScreen=screenBounds.intersectsBounds(featureBounds);}else{var screenPoly=screenBounds.toGeometry();onScreen=screenPoly.intersects(this.geometry);}}
+return onScreen;},createMarker:function(){return null;},destroyMarker:function(){},createPopup:function(){return null;},atPoint:function(lonlat,toleranceLon,toleranceLat){var atPoint=false;if(this.geometry){atPoint=this.geometry.atPoint(lonlat,toleranceLon,toleranceLat);}
+return atPoint;},destroyPopup:function(){},move:function(location){if(!this.layer||!this.geometry.move){return;}
+var pixel;if(location.CLASS_NAME=="OpenLayers.LonLat"){pixel=this.layer.getViewPortPxFromLonLat(location);}else{pixel=location;}
+var lastPixel=this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());var res=this.layer.map.getResolution();this.geometry.move(res*(pixel.x-lastPixel.x),res*(lastPixel.y-pixel.y));this.layer.drawFeature(this);return lastPixel;},toState:function(state){if(state==OpenLayers.State.UPDATE){switch(this.state){case OpenLayers.State.UNKNOWN:case OpenLayers.State.DELETE:this.state=state;break;case OpenLayers.State.UPDATE:case OpenLayers.State.INSERT:break;}}else if(state==OpenLayers.State.INSERT){switch(this.state){case OpenLayers.State.UNKNOWN:break;default:this.state=state;break;}}else if(state==OpenLayers.State.DELETE){switch(this.state){case OpenLayers.State.INSERT:break;case OpenLayers.State.DELETE:break;case OpenLayers.State.UNKNOWN:case OpenLayers.State.UPDATE:this.state=state;break;}}else if(state==OpenLayers.State.UNKNOWN){this.state=state;}},CLASS_NAME:"OpenLayers.Feature.Vector"});OpenLayers.Feature.Vector.style={'default':{fillColor:"#ee9900",fillOpacity:0.4,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"#ee9900",strokeOpacity:1,strokeWidth:1,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit"},'select':{fillColor:"blue",fillOpacity:0.4,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"blue",strokeOpacity:1,strokeWidth:2,strokeLinecap:"round",strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"pointer"},'temporary':{fillColor:"yellow",fillOpacity:0.2,hoverFillColor:"white",hoverFillOpacity:0.8,strokeColor:"yellow",strokeOpacity:1,strokeLinecap:"round",strokeWidth:4,strokeDashstyle:"solid",hoverStrokeColor:"red",hoverStrokeOpacity:1,hoverStrokeWidth:0.2,pointRadius:6,hoverPointRadius:1,hoverPointUnit:"%",pointerEvents:"visiblePainted",cursor:"inherit"}};OpenLayers.Handler.Box=OpenLayers.Class(OpenLayers.Handler,{dragHandler:null,boxDivClassName:'olHandlerBoxZoomBox',boxCharacteristics:null,initialize:function(control,callbacks,options){OpenLayers.Handler.prototype.initialize.apply(this,arguments);var callbacks={"down":this.startBox,"move":this.moveBox,"out":this.removeBox,"up":this.endBox};this.dragHandler=new OpenLayers.Handler.Drag(this,callbacks,{keyMask:this.keyMask});},setMap:function(map){OpenLayers.Handler.prototype.setMap.apply(this,arguments);if(this.dragHandler){this.dragHandler.setMap(map);}},startBox:function(xy){this.zoomBox=OpenLayers.Util.createDiv('zoomBox',this.dragHandler.start);this.zoomBox.className=this.boxDivClassName;this.zoomBox.style.zIndex=this.map.Z_INDEX_BASE["Popup"]-1;this.map.viewPortDiv.appendChild(this.zoomBox);this.map.div.style.cursor="crosshair";},moveBox:function(xy){var startX=this.dragHandler.start.x;var startY=this.dragHandler.start.y;var deltaX=Math.abs(startX-xy.x);var deltaY=Math.abs(startY-xy.y);this.zoomBox.style.width=Math.max(1,deltaX)+"px";this.zoomBox.style.height=Math.max(1,deltaY)+"px";this.zoomBox.style.left=xy.x<startX?xy.x+"px":startX+"px";this.zoomBox.style.top=xy.y<startY?xy.y+"px":startY+"px";var box=this.getBoxCharacteristics(deltaX,deltaY);if(box.newBoxModel){if(xy.x>startX){this.zoomBox.style.width=Math.max(1,deltaX-box.xOffset)+"px";}
+if(xy.y>startY){this.zoomBox.style.height=Math.max(1,deltaY-box.yOffset)+"px";}}},endBox:function(end){var result;if(Math.abs(this.dragHandler.start.x-end.x)>5||Math.abs(this.dragHandler.start.y-end.y)>5){var start=this.dragHandler.start;var top=Math.min(start.y,end.y);var bottom=Math.max(start.y,end.y);var left=Math.min(start.x,end.x);var right=Math.max(start.x,end.x);result=new OpenLayers.Bounds(left,bottom,right,top);}else{result=this.dragHandler.start.clone();}
+this.removeBox();this.map.div.style.cursor="";this.callback("done",[result]);},removeBox:function(){this.map.viewPortDiv.removeChild(this.zoomBox);this.zoomBox=null;this.boxCharacteristics=null;},activate:function(){if(OpenLayers.Handler.prototype.activate.apply(this,arguments)){this.dragHandler.activate();return true;}else{return false;}},deactivate:function(){if(OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){this.dragHandler.deactivate();return true;}else{return false;}},getBoxCharacteristics:function(dx,dy){if(!this.boxCharacteristics){var xOffset=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-left-width"))+parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-right-width"))+1;var yOffset=parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-top-width"))+parseInt(OpenLayers.Element.getStyle(this.zoomBox,"border-bottom-width"))+1;var newBoxModel=OpenLayers.Util.getBrowserName()=="msie"?document.compatMode!="BackCompat":true;this.boxCharacteristics={xOffset:xOffset,yOffset:yOffset,newBoxModel:newBoxModel}}
+return this.boxCharacteristics;},CLASS_NAME:"OpenLayers.Handler.Box"});OpenLayers.Layer.HTTPRequest=OpenLayers.Class(OpenLayers.Layer,{URL_HASH_FACTOR:(Math.sqrt(5)-1)/2,url:null,params:null,reproject:false,initialize:function(name,url,params,options){var newArguments=arguments;newArguments=[name,options];OpenLayers.Layer.prototype.initialize.apply(this,newArguments);this.url=url;this.params=OpenLayers.Util.extend({},params);},destroy:function(){this.url=null;this.params=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.HTTPRequest(this.name,this.url,this.params,this.options);}
+obj=OpenLayers.Layer.prototype.clone.apply(this,[obj]);return obj;},setUrl:function(newUrl){this.url=newUrl;},mergeNewParams:function(newParams){this.params=OpenLayers.Util.extend(this.params,newParams);return this.redraw();},redraw:function(force){if(force){return this.mergeNewParams({"_olSalt":Math.random()});}else{return OpenLayers.Layer.prototype.redraw.apply(this,[]);}},selectUrl:function(paramString,urls){var product=1;for(var i=0,len=paramString.length;i<len;i++){product*=paramString.charCodeAt(i)*this.URL_HASH_FACTOR;product-=Math.floor(product);}
 return urls[Math.floor(product*urls.length)];},getFullRequestString:function(newParams,altUrl){var url=altUrl||this.url;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var paramsString=OpenLayers.Util.getParameterString(allParams);if(url instanceof Array){url=this.selectUrl(paramsString,url);}
 var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}}
 paramsString=OpenLayers.Util.getParameterString(allParams);var requestString=url;if(paramsString!=""){var lastServerChar=url.charAt(url.length-1);if((lastServerChar=="&")||(lastServerChar=="?")){requestString+=paramsString;}else{if(url.indexOf('?')==-1){requestString+='?'+paramsString;}else{requestString+='&'+paramsString;}}}
-return requestString;},CLASS_NAME:"OpenLayers.Layer.HTTPRequest"});OpenLayers.Control.ZoomBox=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,out:false,draw:function(){this.handler=new OpenLayers.Handler.Box(this,{done:this.zoomBox},{keyMask:this.keyMask});},zoomBox:function(position){if(position instanceof OpenLayers.Bounds){if(!this.out){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));var bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);}else{var pixWidth=Math.abs(position.right-position.left);var pixHeight=Math.abs(position.top-position.bottom);var zoomFactor=Math.min((this.map.size.h/pixHeight),(this.map.size.w/pixWidth));var extent=this.map.getExtent();var center=this.map.getLonLatFromPixel(position.getCenterPixel());var xmin=center.lon-(extent.getWidth()/2)*zoomFactor;var xmax=center.lon+(extent.getWidth()/2)*zoomFactor;var ymin=center.lat-(extent.getHeight()/2)*zoomFactor;var ymax=center.lat+(extent.getHeight()/2)*zoomFactor;var bounds=new OpenLayers.Bounds(xmin,ymin,xmax,ymax);}
-this.map.zoomToExtent(bounds);}else{if(!this.out){this.map.setCenter(this.map.getLonLatFromPixel(position),this.map.getZoom()+1);}else{this.map.setCenter(this.map.getLonLatFromPixel(position),this.map.getZoom()-1);}}},CLASS_NAME:"OpenLayers.Control.ZoomBox"});OpenLayers.Layer.Grid=OpenLayers.Class(OpenLayers.Layer.HTTPRequest,{tileSize:null,grid:null,singleTile:false,ratio:1.5,buffer:2,numLoadingTiles:0,initialize:function(name,url,params,options){OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,arguments);this.events.addEventType("tileloaded");this.grid=[];},destroy:function(){this.clearGrid();this.grid=null;this.tileSize=null;OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this,arguments);},clearGrid:function(){if(this.grid){for(var iRow=0;iRow<this.grid.length;iRow++){var row=this.grid[iRow];for(var iCol=0;iCol<row.length;iCol++){var tile=row[iCol];this.removeTileMonitoringHooks(tile);tile.destroy();}}
+return requestString;},CLASS_NAME:"OpenLayers.Layer.HTTPRequest"});OpenLayers.Control.DrawFeature=OpenLayers.Class(OpenLayers.Control,{layer:null,callbacks:null,EVENT_TYPES:["featureadded"],featureAdded:function(){},handlerOptions:null,initialize:function(layer,handler,options){this.EVENT_TYPES=OpenLayers.Control.DrawFeature.prototype.EVENT_TYPES.concat(OpenLayers.Control.prototype.EVENT_TYPES);OpenLayers.Control.prototype.initialize.apply(this,[options]);this.callbacks=OpenLayers.Util.extend({done:this.drawFeature},this.callbacks);this.layer=layer;this.handler=new handler(this,this.callbacks,this.handlerOptions);},drawFeature:function(geometry){var feature=new OpenLayers.Feature.Vector(geometry);this.layer.addFeatures([feature]);this.featureAdded(feature);this.events.triggerEvent("featureadded",{feature:feature});},CLASS_NAME:"OpenLayers.Control.DrawFeature"});OpenLayers.Control.ZoomBox=OpenLayers.Class(OpenLayers.Control,{type:OpenLayers.Control.TYPE_TOOL,out:false,draw:function(){this.handler=new OpenLayers.Handler.Box(this,{done:this.zoomBox},{keyMask:this.keyMask});},zoomBox:function(position){if(position instanceof OpenLayers.Bounds){if(!this.out){var minXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.left,position.bottom));var maxXY=this.map.getLonLatFromPixel(new OpenLayers.Pixel(position.right,position.top));var bounds=new OpenLayers.Bounds(minXY.lon,minXY.lat,maxXY.lon,maxXY.lat);}else{var pixWidth=Math.abs(position.right-position.left);var pixHeight=Math.abs(position.top-position.bottom);var zoomFactor=Math.min((this.map.size.h/pixHeight),(this.map.size.w/pixWidth));var extent=this.map.getExtent();var center=this.map.getLonLatFromPixel(position.getCenterPixel());var xmin=center.lon-(extent.getWidth()/2)*zoomFactor;var xmax=center.lon+(extent.getWidth()/2)*zoomFactor;var ymin=center.lat-(extent.getHeight()/2)*zoomFactor;var ymax=center.lat+(extent.getHeight()/2)*zoomFactor;var bounds=new OpenLayers.Bounds(xmin,ymin,xmax,ymax);}
+this.map.zoomToExtent(bounds);}else{if(!this.out){this.map.setCenter(this.map.getLonLatFromPixel(position),this.map.getZoom()+1);}else{this.map.setCenter(this.map.getLonLatFromPixel(position),this.map.getZoom()-1);}}},CLASS_NAME:"OpenLayers.Control.ZoomBox"});OpenLayers.Format.WKT=OpenLayers.Class(OpenLayers.Format,{initialize:function(options){this.regExes={'typeStr':/^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,'spaces':/\s+/,'parenComma':/\)\s*,\s*\(/,'doubleParenComma':/\)\s*\)\s*,\s*\(\s*\(/,'trimParens':/^\s*\(?(.*?)\)?\s*$/};OpenLayers.Format.prototype.initialize.apply(this,[options]);},read:function(wkt){var features,type,str;var matches=this.regExes.typeStr.exec(wkt);if(matches){type=matches[1].toLowerCase();str=matches[2];if(this.parse[type]){features=this.parse[type].apply(this,[str]);}
+if(this.internalProjection&&this.externalProjection){if(features&&features.CLASS_NAME=="OpenLayers.Feature.Vector"){features.geometry.transform(this.externalProjection,this.internalProjection);}else if(features&&type!="geometrycollection"&&typeof features=="object"){for(var i=0,len=features.length;i<len;i++){var component=features[i];component.geometry.transform(this.externalProjection,this.internalProjection);}}}}
+return features;},write:function(features){var collection,geometry,type,data,isCollection;if(features.constructor==Array){collection=features;isCollection=true;}else{collection=[features];isCollection=false;}
+var pieces=[];if(isCollection){pieces.push('GEOMETRYCOLLECTION(');}
+for(var i=0,len=collection.length;i<len;++i){if(isCollection&&i>0){pieces.push(',');}
+geometry=collection[i].geometry;type=geometry.CLASS_NAME.split('.')[2].toLowerCase();if(!this.extract[type]){return null;}
+if(this.internalProjection&&this.externalProjection){geometry=geometry.clone();geometry.transform(this.internalProjection,this.externalProjection);}
+data=this.extract[type].apply(this,[geometry]);pieces.push(type.toUpperCase()+'('+data+')');}
+if(isCollection){pieces.push(')');}
+return pieces.join('');},extract:{'point':function(point){return point.x+' '+point.y;},'multipoint':function(multipoint){var array=[];for(var i=0,len=multipoint.components.length;i<len;++i){array.push(this.extract.point.apply(this,[multipoint.components[i]]));}
+return array.join(',');},'linestring':function(linestring){var array=[];for(var i=0,len=linestring.components.length;i<len;++i){array.push(this.extract.point.apply(this,[linestring.components[i]]));}
+return array.join(',');},'multilinestring':function(multilinestring){var array=[];for(var i=0,len=multilinestring.components.length;i<len;++i){array.push('('+
+this.extract.linestring.apply(this,[multilinestring.components[i]])+')');}
+return array.join(',');},'polygon':function(polygon){var array=[];for(var i=0,len=polygon.components.length;i<len;++i){array.push('('+
+this.extract.linestring.apply(this,[polygon.components[i]])+')');}
+return array.join(',');},'multipolygon':function(multipolygon){var array=[];for(var i=0,len=multipolygon.components.length;i<len;++i){array.push('('+
+this.extract.polygon.apply(this,[multipolygon.components[i]])+')');}
+return array.join(',');}},parse:{'point':function(str){var coords=OpenLayers.String.trim(str).split(this.regExes.spaces);return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point(coords[0],coords[1]));},'multipoint':function(str){var points=OpenLayers.String.trim(str).split(',');var components=[];for(var i=0,len=points.length;i<len;++i){components.push(this.parse.point.apply(this,[points[i]]).geometry);}
+return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPoint(components));},'linestring':function(str){var points=OpenLayers.String.trim(str).split(',');var components=[];for(var i=0,len=points.length;i<len;++i){components.push(this.parse.point.apply(this,[points[i]]).geometry);}
+return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString(components));},'multilinestring':function(str){var line;var lines=OpenLayers.String.trim(str).split(this.regExes.parenComma);var components=[];for(var i=0,len=lines.length;i<len;++i){line=lines[i].replace(this.regExes.trimParens,'$1');components.push(this.parse.linestring.apply(this,[line]).geometry);}
+return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiLineString(components));},'polygon':function(str){var ring,linestring,linearring;var rings=OpenLayers.String.trim(str).split(this.regExes.parenComma);var components=[];for(var i=0,len=rings.length;i<len;++i){ring=rings[i].replace(this.regExes.trimParens,'$1');linestring=this.parse.linestring.apply(this,[ring]).geometry;linearring=new OpenLayers.Geometry.LinearRing(linestring.components);components.push(linearring);}
+return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon(components));},'multipolygon':function(str){var polygon;var polygons=OpenLayers.String.trim(str).split(this.regExes.doubleParenComma);var components=[];for(var i=0,len=polygons.length;i<len;++i){polygon=polygons[i].replace(this.regExes.trimParens,'$1');components.push(this.parse.polygon.apply(this,[polygon]).geometry);}
+return new OpenLayers.Feature.Vector(new OpenLayers.Geometry.MultiPolygon(components));},'geometrycollection':function(str){str=str.replace(/,\s*([A-Za-z])/g,'|$1');var wktArray=OpenLayers.String.trim(str).split('|');var components=[];for(var i=0,len=wktArray.length;i<len;++i){components.push(OpenLayers.Format.WKT.prototype.read.apply(this,[wktArray[i]]));}
+return components;}},CLASS_NAME:"OpenLayers.Format.WKT"});OpenLayers.Layer.Grid=OpenLayers.Class(OpenLayers.Layer.HTTPRequest,{tileSize:null,grid:null,singleTile:false,ratio:1.5,buffer:2,numLoadingTiles:0,initialize:function(name,url,params,options){OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this,arguments);this.events.addEventType("tileloaded");this.grid=[];},destroy:function(){this.clearGrid();this.grid=null;this.tileSize=null;OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this,arguments);},clearGrid:function(){if(this.grid){for(var iRow=0,len=this.grid.length;iRow<len;iRow++){var row=this.grid[iRow];for(var iCol=0,clen=row.length;iCol<clen;iCol++){var tile=row[iCol];this.removeTileMonitoringHooks(tile);tile.destroy();}}
 this.grid=[];}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.Grid(this.name,this.url,this.params,this.options);}
 obj=OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this,[obj]);if(this.tileSize!=null){obj.tileSize=this.tileSize.clone();}
 obj.grid=[];return obj;},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this,arguments);bounds=bounds||this.map.getExtent();if(bounds!=null){var forceReTile=!this.grid.length||zoomChanged;var tilesBounds=this.getTilesBounds();if(this.singleTile){if(forceReTile||(!dragging&&!tilesBounds.containsBounds(bounds))){this.initSingleTile(bounds);}}else{if(forceReTile||!tilesBounds.containsBounds(bounds,true)){this.initGriddedTiles(bounds);}else{this.moveGriddedTiles(bounds);}}}},setTileSize:function(size){if(this.singleTile){size=this.map.getSize().clone();size.h=parseInt(size.h*this.ratio);size.w=parseInt(size.w*this.ratio);}
@@ -496,34 +629,138 @@
 var tile=this.grid[0][0];if(!tile){tile=this.addTile(tileBounds,px);this.addTileMonitoringHooks(tile);tile.draw();this.grid[0][0]=tile;}else{tile.moveTo(tileBounds,px);}
 this.removeExcessTiles(1,1);},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left-extent.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=extent.left+tilecol*tilelon;var offsetlat=bounds.top-(extent.bottom+tilelat);var tilerow=Math.ceil(offsetlat/tilelat)+this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=-tilerowremain*this.tileSize.h;var tileoffsetlat=extent.bottom+tilerow*tilelat;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},initGriddedTiles:function(bounds){var viewSize=this.map.getSize();var minRows=Math.ceil(viewSize.h/this.tileSize.h)+
 Math.max(1,2*this.buffer);var minCols=Math.ceil(viewSize.w/this.tileSize.w)+
-Math.max(1,2*this.buffer);var extent=this.map.getMaxExtent();var resolution=this.map.getResolution();var tileLayout=this.calculateGridLayout(bounds,extent,resolution);var tileoffsetx=Math.round(tileLayout.tileoffsetx);var tileoffsety=Math.round(tileLayout.tileoffsety);var tileoffsetlon=tileLayout.tileoffsetlon;var tileoffsetlat=tileLayout.tileoffsetlat;var tilelon=tileLayout.tilelon;var tilelat=tileLayout.tilelat;this.origin=new OpenLayers.Pixel(tileoffsetx,tileoffsety);var startX=tileoffsetx;var startLon=tileoffsetlon;var rowidx=0;var layerContainerDivLeft=parseInt(this.map.layerContainerDiv.style.left);var layerContainerDivTop=parseInt(this.map.layerContainerDiv.style.top);do{var row=this.grid[rowidx++];if(!row){row=[];this.grid.push(row);}
+Math.max(1,2*this.buffer);var extent=this.maxExtent;var resolution=this.map.getResolution();var tileLayout=this.calculateGridLayout(bounds,extent,resolution);var tileoffsetx=Math.round(tileLayout.tileoffsetx);var tileoffsety=Math.round(tileLayout.tileoffsety);var tileoffsetlon=tileLayout.tileoffsetlon;var tileoffsetlat=tileLayout.tileoffsetlat;var tilelon=tileLayout.tilelon;var tilelat=tileLayout.tilelat;this.origin=new OpenLayers.Pixel(tileoffsetx,tileoffsety);var startX=tileoffsetx;var startLon=tileoffsetlon;var rowidx=0;var layerContainerDivLeft=parseInt(this.map.layerContainerDiv.style.left);var layerContainerDivTop=parseInt(this.map.layerContainerDiv.style.top);do{var row=this.grid[rowidx++];if(!row){row=[];this.grid.push(row);}
 tileoffsetlon=startLon;tileoffsetx=startX;var colidx=0;do{var tileBounds=new OpenLayers.Bounds(tileoffsetlon,tileoffsetlat,tileoffsetlon+tilelon,tileoffsetlat+tilelat);var x=tileoffsetx;x-=layerContainerDivLeft;var y=tileoffsety;y-=layerContainerDivTop;var px=new OpenLayers.Pixel(x,y);var tile=row[colidx++];if(!tile){tile=this.addTile(tileBounds,px);this.addTileMonitoringHooks(tile);row.push(tile);}else{tile.moveTo(tileBounds,px,false);}
 tileoffsetlon+=tilelon;tileoffsetx+=this.tileSize.w;}while((tileoffsetlon<=bounds.right+tilelon*this.buffer)||colidx<minCols)
 tileoffsetlat-=tilelat;tileoffsety+=this.tileSize.h;}while((tileoffsetlat>=bounds.bottom-tilelat*this.buffer)||rowidx<minRows)
 this.removeExcessTiles(rowidx,colidx);this.spiralTileLoad();},spiralTileLoad:function(){var tileQueue=[];var directions=["right","down","left","up"];var iRow=0;var iCell=-1;var direction=OpenLayers.Util.indexOf(directions,"right");var directionsTried=0;while(directionsTried<directions.length){var testRow=iRow;var testCell=iCell;switch(directions[direction]){case"right":testCell++;break;case"down":testRow++;break;case"left":testCell--;break;case"up":testRow--;break;}
 var tile=null;if((testRow<this.grid.length)&&(testRow>=0)&&(testCell<this.grid[0].length)&&(testCell>=0)){tile=this.grid[testRow][testCell];}
 if((tile!=null)&&(!tile.queued)){tileQueue.unshift(tile);tile.queued=true;directionsTried=0;iRow=testRow;iCell=testCell;}else{direction=(direction+1)%4;directionsTried++;}}
-for(var i=0;i<tileQueue.length;i++){var tile=tileQueue[i];tile.draw();tile.queued=false;}},addTile:function(bounds,position){},addTileMonitoringHooks:function(tile){tile.onLoadStart=function(){if(this.numLoadingTiles==0){this.events.triggerEvent("loadstart");}
-this.numLoadingTiles++;};tile.events.register("loadstart",this,tile.onLoadStart);tile.onLoadEnd=function(){this.numLoadingTiles--;this.events.triggerEvent("tileloaded");if(this.numLoadingTiles==0){this.events.triggerEvent("loadend");}};tile.events.register("loadend",this,tile.onLoadEnd);tile.events.register("unload",this,tile.onLoadEnd);},removeTileMonitoringHooks:function(tile){tile.unload();tile.events.un({"loadstart":tile.onLoadStart,"loadend":tile.onLoadEnd,"unload":tile.onLoadEnd,scope:this});},moveGriddedTiles:function(bounds){var buffer=this.buffer||1;while(true){var tlLayer=this.grid[0][0].position;var tlViewPort=this.map.getViewPortPxFromLayerPx(tlLayer);if(tlViewPort.x>-this.tileSize.w*(buffer-1)){this.shiftColumn(true);}else if(tlViewPort.x<-this.tileSize.w*buffer){this.shiftColumn(false);}else if(tlViewPort.y>-this.tileSize.h*(buffer-1)){this.shiftRow(true);}else if(tlViewPort.y<-this.tileSize.h*buffer){this.shiftRow(false);}else{break;}};},shiftRow:function(prepend){var modelRowIndex=(prepend)?0:(this.grid.length-1);var grid=this.grid;var modelRow=grid[modelRowIndex];var resolution=this.map.getResolution();var deltaY=(prepend)?-this.tileSize.h:this.tileSize.h;var deltaLat=resolution*-deltaY;var row=(prepend)?grid.pop():grid.shift();for(var i=0;i<modelRow.length;i++){var modelTile=modelRow[i];var bounds=modelTile.bounds.clone();var position=modelTile.position.clone();bounds.bottom=bounds.bottom+deltaLat;bounds.top=bounds.top+deltaLat;position.y=position.y+deltaY;row[i].moveTo(bounds,position);}
-if(prepend){grid.unshift(row);}else{grid.push(row);}},shiftColumn:function(prepend){var deltaX=(prepend)?-this.tileSize.w:this.tileSize.w;var resolution=this.map.getResolution();var deltaLon=resolution*deltaX;for(var i=0;i<this.grid.length;i++){var row=this.grid[i];var modelTileIndex=(prepend)?0:(row.length-1);var modelTile=row[modelTileIndex];var bounds=modelTile.bounds.clone();var position=modelTile.position.clone();bounds.left=bounds.left+deltaLon;bounds.right=bounds.right+deltaLon;position.x=position.x+deltaX;var tile=prepend?this.grid[i].pop():this.grid[i].shift();tile.moveTo(bounds,position);if(prepend){row.unshift(tile);}else{row.push(tile);}}},removeExcessTiles:function(rows,columns){while(this.grid.length>rows){var row=this.grid.pop();for(var i=0,l=row.length;i<l;i++){var tile=row[i];this.removeTileMonitoringHooks(tile);tile.destroy();}}
-while(this.grid[0].length>columns){for(var i=0,l=this.grid.length;i<l;i++){var row=this.grid[i];var tile=row.pop();this.removeTileMonitoringHooks(tile);tile.destroy();}}},onMapResize:function(){if(this.singleTile){this.clearGrid();this.setTileSize();}},getTileBounds:function(viewPortPx){var maxExtent=this.map.getMaxExtent();var resolution=this.getResolution();var tileMapWidth=resolution*this.tileSize.w;var tileMapHeight=resolution*this.tileSize.h;var mapPoint=this.getLonLatFromViewPortPx(viewPortPx);var tileLeft=maxExtent.left+(tileMapWidth*Math.floor((mapPoint.lon-
+for(var i=0,len=tileQueue.length;i<len;i++){var tile=tileQueue[i];tile.draw();tile.queued=false;}},addTile:function(bounds,position){},addTileMonitoringHooks:function(tile){tile.onLoadStart=function(){if(this.numLoadingTiles==0){this.events.triggerEvent("loadstart");}
+this.numLoadingTiles++;};tile.events.register("loadstart",this,tile.onLoadStart);tile.onLoadEnd=function(){this.numLoadingTiles--;this.events.triggerEvent("tileloaded");if(this.numLoadingTiles==0){this.events.triggerEvent("loadend");}};tile.events.register("loadend",this,tile.onLoadEnd);tile.events.register("unload",this,tile.onLoadEnd);},removeTileMonitoringHooks:function(tile){tile.unload();tile.events.un({"loadstart":tile.onLoadStart,"loadend":tile.onLoadEnd,"unload":tile.onLoadEnd,scope:this});},moveGriddedTiles:function(bounds){var buffer=this.buffer||1;while(true){var tlLayer=this.grid[0][0].position;var tlViewPort=this.map.getViewPortPxFromLayerPx(tlLayer);if(tlViewPort.x>-this.tileSize.w*(buffer-1)){this.shiftColumn(true);}else if(tlViewPort.x<-this.tileSize.w*buffer){this.shiftColumn(false);}else if(tlViewPort.y>-this.tileSize.h*(buffer-1)){this.shiftRow(true);}else if(tlViewPort.y<-this.tileSize.h*buffer){this.shiftRow(false);}else{break;}};},shiftRow:function(prepend){var modelRowIndex=(prepend)?0:(this.grid.length-1);var grid=this.grid;var modelRow=grid[modelRowIndex];var resolution=this.map.getResolution();var deltaY=(prepend)?-this.tileSize.h:this.tileSize.h;var deltaLat=resolution*-deltaY;var row=(prepend)?grid.pop():grid.shift();for(var i=0,len=modelRow.length;i<len;i++){var modelTile=modelRow[i];var bounds=modelTile.bounds.clone();var position=modelTile.position.clone();bounds.bottom=bounds.bottom+deltaLat;bounds.top=bounds.top+deltaLat;position.y=position.y+deltaY;row[i].moveTo(bounds,position);}
+if(prepend){grid.unshift(row);}else{grid.push(row);}},shiftColumn:function(prepend){var deltaX=(prepend)?-this.tileSize.w:this.tileSize.w;var resolution=this.map.getResolution();var deltaLon=resolution*deltaX;for(var i=0,len=this.grid.length;i<len;i++){var row=this.grid[i];var modelTileIndex=(prepend)?0:(row.length-1);var modelTile=row[modelTileIndex];var bounds=modelTile.bounds.clone();var position=modelTile.position.clone();bounds.left=bounds.left+deltaLon;bounds.right=bounds.right+deltaLon;position.x=position.x+deltaX;var tile=prepend?this.grid[i].pop():this.grid[i].shift();tile.moveTo(bounds,position);if(prepend){row.unshift(tile);}else{row.push(tile);}}},removeExcessTiles:function(rows,columns){while(this.grid.length>rows){var row=this.grid.pop();for(var i=0,l=row.length;i<l;i++){var tile=row[i];this.removeTileMonitoringHooks(tile);tile.destroy();}}
+while(this.grid[0].length>columns){for(var i=0,l=this.grid.length;i<l;i++){var row=this.grid[i];var tile=row.pop();this.removeTileMonitoringHooks(tile);tile.destroy();}}},onMapResize:function(){if(this.singleTile){this.clearGrid();this.setTileSize();}},getTileBounds:function(viewPortPx){var maxExtent=this.maxExtent;var resolution=this.getResolution();var tileMapWidth=resolution*this.tileSize.w;var tileMapHeight=resolution*this.tileSize.h;var mapPoint=this.getLonLatFromViewPortPx(viewPortPx);var tileLeft=maxExtent.left+(tileMapWidth*Math.floor((mapPoint.lon-
 maxExtent.left)/tileMapWidth));var tileBottom=maxExtent.bottom+(tileMapHeight*Math.floor((mapPoint.lat-
-maxExtent.bottom)/tileMapHeight));return new OpenLayers.Bounds(tileLeft,tileBottom,tileLeft+tileMapWidth,tileBottom+tileMapHeight);},CLASS_NAME:"OpenLayers.Layer.Grid"});OpenLayers.Control.Navigation=OpenLayers.Class(OpenLayers.Control,{dragPan:null,zoomBox:null,zoomWheelEnabled:true,initialize:function(options){this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){this.deactivate();if(this.dragPan){this.dragPan.destroy();}
+maxExtent.bottom)/tileMapHeight));return new OpenLayers.Bounds(tileLeft,tileBottom,tileLeft+tileMapWidth,tileBottom+tileMapHeight);},CLASS_NAME:"OpenLayers.Layer.Grid"});OpenLayers.Style=OpenLayers.Class({name:null,title:null,description:null,layerName:null,isDefault:false,rules:null,context:null,defaultStyle:null,propertyStyles:null,initialize:function(style,options){this.rules=[];this.setDefaultStyle(style||OpenLayers.Feature.Vector.style["default"]);OpenLayers.Util.extend(this,options);},destroy:function(){for(var i=0,len=this.rules.length;i<len;i++){this.rules[i].destroy();this.rules[i]=null;}
+this.rules=null;this.defaultStyle=null;},createSymbolizer:function(feature){var style=this.createLiterals(OpenLayers.Util.extend({},this.defaultStyle),feature);var rules=this.rules;var rule,context;var elseRules=[];var appliedRules=false;for(var i=0,len=rules.length;i<len;i++){rule=rules[i];var applies=rule.evaluate(feature);if(applies){if(rule instanceof OpenLayers.Rule&&rule.elseFilter){elseRules.push(rule);}else{appliedRules=true;this.applySymbolizer(rule,style,feature);}}}
+if(appliedRules==false&&elseRules.length>0){appliedRules=true;for(var i=0,len=elseRules.length;i<len;i++){this.applySymbolizer(elseRules[i],style,feature);}}
+if(rules.length>0&&appliedRules==false){style.display="none";}else{style.display="";}
+return style;},applySymbolizer:function(rule,style,feature){var symbolizerPrefix=feature.geometry?this.getSymbolizerPrefix(feature.geometry):OpenLayers.Style.SYMBOLIZER_PREFIXES[0];var symbolizer=rule.symbolizer[symbolizerPrefix]||rule.symbolizer;return this.createLiterals(OpenLayers.Util.extend(style,symbolizer),feature);},createLiterals:function(style,feature){var context=this.context||feature.attributes||feature.data;for(var i in this.propertyStyles){style[i]=OpenLayers.Style.createLiteral(style[i],context,feature);}
+return style;},findPropertyStyles:function(){var propertyStyles={};var style=this.defaultStyle;this.addPropertyStyles(propertyStyles,style);var rules=this.rules;var symbolizer,value;for(var i=0,len=rules.length;i<len;i++){var symbolizer=rules[i].symbolizer;for(var key in symbolizer){value=symbolizer[key];if(typeof value=="object"){this.addPropertyStyles(propertyStyles,value);}else{this.addPropertyStyles(propertyStyles,symbolizer);break;}}}
+return propertyStyles;},addPropertyStyles:function(propertyStyles,symbolizer){var property;for(var key in symbolizer){property=symbolizer[key];if(typeof property=="string"&&property.match(/\$\{\w+\}/)){propertyStyles[key]=true;}}
+return propertyStyles;},addRules:function(rules){this.rules=this.rules.concat(rules);this.propertyStyles=this.findPropertyStyles();},setDefaultStyle:function(style){this.defaultStyle=style;this.propertyStyles=this.findPropertyStyles();},getSymbolizerPrefix:function(geometry){var prefixes=OpenLayers.Style.SYMBOLIZER_PREFIXES;for(var i=0,len=prefixes.length;i<len;i++){if(geometry.CLASS_NAME.indexOf(prefixes[i])!=-1){return prefixes[i];}}},CLASS_NAME:"OpenLayers.Style"});OpenLayers.Style.createLiteral=function(value,context,feature){if(typeof value=="string"&&value.indexOf("${")!=-1){value=OpenLayers.String.format(value,context,[feature]);value=(isNaN(value)||!value)?value:parseFloat(value);}
+return value;};OpenLayers.Style.SYMBOLIZER_PREFIXES=['Point','Line','Polygon','Text'];OpenLayers.Control.Navigation=OpenLayers.Class(OpenLayers.Control,{dragPan:null,dragPanOptions:null,zoomBox:null,zoomWheelEnabled:true,handleRightClicks:true,initialize:function(options){this.handlers={};OpenLayers.Control.prototype.initialize.apply(this,arguments);},destroy:function(){this.deactivate();if(this.dragPan){this.dragPan.destroy();}
 this.dragPan=null;if(this.zoomBox){this.zoomBox.destroy();}
 this.zoomBox=null;OpenLayers.Control.prototype.destroy.apply(this,arguments);},activate:function(){this.dragPan.activate();if(this.zoomWheelEnabled){this.handlers.wheel.activate();}
-this.handlers.click.activate();this.zoomBox.activate();return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){this.zoomBox.deactivate();this.dragPan.deactivate();this.handlers.click.deactivate();this.handlers.wheel.deactivate();return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},draw:function(){this.handlers.click=new OpenLayers.Handler.Click(this,{'dblclick':this.defaultDblClick},{'double':true,'stopDouble':true});this.dragPan=new OpenLayers.Control.DragPan({map:this.map});this.zoomBox=new OpenLayers.Control.ZoomBox({map:this.map,keyMask:OpenLayers.Handler.MOD_SHIFT});this.dragPan.draw();this.zoomBox.draw();this.handlers.wheel=new OpenLayers.Handler.MouseWheel(this,{"up":this.wheelUp,"down":this.wheelDown});this.activate();},defaultDblClick:function(evt){var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom+1);},wheelChange:function(evt,deltaZ){var newZoom=this.map.getZoom()+deltaZ;if(!this.map.isValidZoomLevel(newZoom)){return;}
-var size=this.map.getSize();var deltaX=size.w/2-evt.xy.x;var deltaY=evt.xy.y-size.h/2;var newRes=this.map.baseLayer.getResolutionForZoom(newZoom);var zoomPoint=this.map.getLonLatFromPixel(evt.xy);var newCenter=new OpenLayers.LonLat(zoomPoint.lon+deltaX*newRes,zoomPoint.lat+deltaY*newRes);this.map.setCenter(newCenter,newZoom);},wheelUp:function(evt){this.wheelChange(evt,1);},wheelDown:function(evt){this.wheelChange(evt,-1);},disableZoomWheel:function(){this.zoomWheelEnabled=false;this.handlers.wheel.deactivate();},enableZoomWheel:function(){this.zoomWheelEnabled=true;if(this.active){this.handlers.wheel.activate();}},CLASS_NAME:"OpenLayers.Control.Navigation"});OpenLayers.Layer.MapGuide=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,singleTile:false,TILE_PARAMS:{operation:'GETTILEIMAGE',version:'1.2.0'},SINGLE_TILE_PARAMS:{operation:'GETMAPIMAGE',format:'PNG',locale:'en',clip:'1',version:'1.0.0'},defaultSize:new OpenLayers.Size(300,300),initialize:function(name,url,params,options){OpenLayers.Layer.Grid.prototype.initialize.apply(this,arguments);if(options==null||options.isBaseLayer==null){this.isBaseLayer=((this.transparent!="true")&&(this.transparent!=true));}
+this.handlers.click.activate();this.zoomBox.activate();return OpenLayers.Control.prototype.activate.apply(this,arguments);},deactivate:function(){this.zoomBox.deactivate();this.dragPan.deactivate();this.handlers.click.deactivate();this.handlers.wheel.deactivate();return OpenLayers.Control.prototype.deactivate.apply(this,arguments);},draw:function(){if(this.handleRightClicks){this.map.div.oncontextmenu=function(){return false;};}
+var clickCallbacks={'dblclick':this.defaultDblClick,'dblrightclick':this.defaultDblRightClick};var clickOptions={'double':true,'stopDouble':true};this.handlers.click=new OpenLayers.Handler.Click(this,clickCallbacks,clickOptions);this.dragPan=new OpenLayers.Control.DragPan(OpenLayers.Util.extend({map:this.map},this.dragPanOptions));this.zoomBox=new OpenLayers.Control.ZoomBox({map:this.map,keyMask:OpenLayers.Handler.MOD_SHIFT});this.dragPan.draw();this.zoomBox.draw();this.handlers.wheel=new OpenLayers.Handler.MouseWheel(this,{"up":this.wheelUp,"down":this.wheelDown});this.activate();},defaultDblClick:function(evt){var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom+1);},defaultDblRightClick:function(evt){var newCenter=this.map.getLonLatFromViewPortPx(evt.xy);this.map.setCenter(newCenter,this.map.zoom-1);},wheelChange:function(evt,deltaZ){var newZoom=this.map.getZoom()+deltaZ;if(!this.map.isValidZoomLevel(newZoom)){return;}
+var size=this.map.getSize();var deltaX=size.w/2-evt.xy.x;var deltaY=evt.xy.y-size.h/2;var newRes=this.map.baseLayer.getResolutionForZoom(newZoom);var zoomPoint=this.map.getLonLatFromPixel(evt.xy);var newCenter=new OpenLayers.LonLat(zoomPoint.lon+deltaX*newRes,zoomPoint.lat+deltaY*newRes);this.map.setCenter(newCenter,newZoom);},wheelUp:function(evt){this.wheelChange(evt,1);},wheelDown:function(evt){this.wheelChange(evt,-1);},disableZoomWheel:function(){this.zoomWheelEnabled=false;this.handlers.wheel.deactivate();},enableZoomWheel:function(){this.zoomWheelEnabled=true;if(this.active){this.handlers.wheel.activate();}},CLASS_NAME:"OpenLayers.Control.Navigation"});OpenLayers.Geometry=OpenLayers.Class({id:null,parent:null,bounds:null,initialize:function(){this.id=OpenLayers.Util.createUniqueID(this.CLASS_NAME+"_");},destroy:function(){this.id=null;this.bounds=null;},clone:function(){return new OpenLayers.Geometry();},setBounds:function(bounds){if(bounds){this.bounds=bounds.clone();}},clearBounds:function(){this.bounds=null;if(this.parent){this.parent.clearBounds();}},extendBounds:function(newBounds){var bounds=this.getBounds();if(!bounds){this.setBounds(newBounds);}else{this.bounds.extend(newBounds);}},getBounds:function(){if(this.bounds==null){this.calculateBounds();}
+return this.bounds;},calculateBounds:function(){},atPoint:function(lonlat,toleranceLon,toleranceLat){var atPoint=false;var bounds=this.getBounds();if((bounds!=null)&&(lonlat!=null)){var dX=(toleranceLon!=null)?toleranceLon:0;var dY=(toleranceLat!=null)?toleranceLat:0;var toleranceBounds=new OpenLayers.Bounds(this.bounds.left-dX,this.bounds.bottom-dY,this.bounds.right+dX,this.bounds.top+dY);atPoint=toleranceBounds.containsLonLat(lonlat);}
+return atPoint;},getLength:function(){return 0.0;},getArea:function(){return 0.0;},toString:function(){return OpenLayers.Format.WKT.prototype.write(new OpenLayers.Feature.Vector(this));},CLASS_NAME:"OpenLayers.Geometry"});OpenLayers.Geometry.segmentsIntersect=function(seg1,seg2,point){var intersection=false;var x11_21=seg1.x1-seg2.x1;var y11_21=seg1.y1-seg2.y1;var x12_11=seg1.x2-seg1.x1;var y12_11=seg1.y2-seg1.y1;var y22_21=seg2.y2-seg2.y1;var x22_21=seg2.x2-seg2.x1;var d=(y22_21*x12_11)-(x22_21*y12_11);var n1=(x22_21*y11_21)-(y22_21*x11_21);var n2=(x12_11*y11_21)-(y12_11*x11_21);if(d==0){if(n1==0&&n2==0){intersection=true;}}else{var along1=n1/d;var along2=n2/d;if(along1>=0&&along1<=1&&along2>=0&&along2<=1){if(!point){intersection=true;}else{var x=seg1.x1+(along1*x12_11);var y=seg1.y1+(along1*y12_11);intersection=new OpenLayers.Geometry.Point(x,y);}}}
+return intersection;};OpenLayers.Layer.MapGuide=OpenLayers.Class(OpenLayers.Layer.Grid,{isBaseLayer:true,singleTile:false,TILE_PARAMS:{operation:'GETTILEIMAGE',version:'1.2.0'},SINGLE_TILE_PARAMS:{operation:'GETMAPIMAGE',format:'PNG',locale:'en',clip:'1',version:'1.0.0'},defaultSize:new OpenLayers.Size(300,300),initialize:function(name,url,params,options){OpenLayers.Layer.Grid.prototype.initialize.apply(this,arguments);if(options==null||options.isBaseLayer==null){this.isBaseLayer=((this.transparent!="true")&&(this.transparent!=true));}
 if(this.singleTile){OpenLayers.Util.applyDefaults(this.params,this.SINGLE_TILE_PARAMS);}else{OpenLayers.Util.applyDefaults(this.params,this.TILE_PARAMS);this.setTileSize(this.defaultSize);}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapGuide(this.name,this.url,this.params,this.options);}
-obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getURL:function(bounds){var url;var center=bounds.getCenterLonLat();var mapSize=this.map.getCurrentSize();if(this.singleTile){var params={};params.setdisplaydpi=OpenLayers.DOTS_PER_INCH;params.setdisplayheight=mapSize.h*this.ratio;params.setdisplaywidth=mapSize.w*this.ratio;params.setviewcenterx=center.lon;params.setviewcentery=center.lat;params.setviewscale=this.map.getScale();if(!this.isBaseLayer){this.params.operation="GETDYNAMICMAPOVERLAYIMAGE";var getVisParams={};getVisParams.operation="GETVISIBLEMAPEXTENT";getVisParams.version="1.0.0";getVisParams.session=this.params.session;getVisParams.mapName=this.params.mapName;getVisParams.format='text/xml';getVisParams=OpenLayers.Util.extend(getVisParams,params);new OpenLayers.Ajax.Request(this.url,{parameters:getVisParams,method:'get',asynchronous:false});}
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getURL:function(bounds){var url;var center=bounds.getCenterLonLat();var mapSize=this.map.getCurrentSize();if(this.singleTile){var params={};params.setdisplaydpi=OpenLayers.DOTS_PER_INCH;params.setdisplayheight=mapSize.h*this.ratio;params.setdisplaywidth=mapSize.w*this.ratio;params.setviewcenterx=center.lon;params.setviewcentery=center.lat;params.setviewscale=this.map.getScale();if(!this.isBaseLayer){this.params.operation="GETDYNAMICMAPOVERLAYIMAGE";var getVisParams={};getVisParams=OpenLayers.Util.extend(getVisParams,params);getVisParams.operation="GETVISIBLEMAPEXTENT";getVisParams.version="1.0.0";getVisParams.session=this.params.session;getVisParams.mapName=this.params.mapName;getVisParams.format='text/xml';url=this.getFullRequestString(getVisParams);OpenLayers.Request.GET({url:url,async:false});}
 url=this.getFullRequestString(params);}else{var currentRes=this.map.getResolution();var colidx=Math.floor((bounds.left-this.maxExtent.left)/currentRes);colidx=Math.round(colidx/this.tileSize.w);var rowidx=Math.floor((this.maxExtent.top-bounds.top)/currentRes);rowidx=Math.round(rowidx/this.tileSize.h);url=this.getFullRequestString({tilecol:colidx,tilerow:rowidx,scaleindex:this.resolutions.length-this.map.zoom-1});}
 return url;},getFullRequestString:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;if(typeof url=="object"){url=url[Math.floor(Math.random()*url.length)];}
 var requestString=url;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getArgs(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}}
 var paramsString=OpenLayers.Util.getParameterString(allParams);paramsString=paramsString.replace(/,/g,"+");if(paramsString!=""){var lastServerChar=url.charAt(url.length-1);if((lastServerChar=="&")||(lastServerChar=="?")){requestString+=paramsString;}else{if(url.indexOf('?')==-1){requestString+='?'+paramsString;}else{requestString+='&'+paramsString;}}}
-return requestString;},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left-extent.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=extent.left+tilecol*tilelon;var offsetlat=extent.top-bounds.top+tilelat;var tilerow=Math.floor(offsetlat/tilelat)-this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=tilerowremain*this.tileSize.h;var tileoffsetlat=extent.top-tilelat*tilerow;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},CLASS_NAME:"OpenLayers.Layer.MapGuide"});OpenLayers.Layer.MapServer=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{mode:"map",map_imagetype:"png"},initialize:function(name,url,params,options){var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);if(arguments.length>0){OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);}
-if(options==null||options.isBaseLayer==null){this.isBaseLayer=((this.params.transparent!="true")&&(this.params.transparent!=true));}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapServer(this.name,this.url,this.params,this.options);}
+return requestString;},calculateGridLayout:function(bounds,extent,resolution){var tilelon=resolution*this.tileSize.w;var tilelat=resolution*this.tileSize.h;var offsetlon=bounds.left-extent.left;var tilecol=Math.floor(offsetlon/tilelon)-this.buffer;var tilecolremain=offsetlon/tilelon-tilecol;var tileoffsetx=-tilecolremain*this.tileSize.w;var tileoffsetlon=extent.left+tilecol*tilelon;var offsetlat=extent.top-bounds.top+tilelat;var tilerow=Math.floor(offsetlat/tilelat)-this.buffer;var tilerowremain=tilerow-offsetlat/tilelat;var tileoffsety=tilerowremain*this.tileSize.h;var tileoffsetlat=extent.top-tilelat*tilerow;return{tilelon:tilelon,tilelat:tilelat,tileoffsetlon:tileoffsetlon,tileoffsetlat:tileoffsetlat,tileoffsetx:tileoffsetx,tileoffsety:tileoffsety};},CLASS_NAME:"OpenLayers.Layer.MapGuide"});OpenLayers.Layer.MapServer=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{mode:"map",map_imagetype:"png"},initialize:function(name,url,params,options){var newArguments=[];newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);this.params=OpenLayers.Util.applyDefaults(this.params,this.DEFAULT_PARAMS);if(options==null||options.isBaseLayer==null){this.isBaseLayer=((this.params.transparent!="true")&&(this.params.transparent!=true));}},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.MapServer(this.name,this.url,this.params,this.options);}
 obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},getURL:function(bounds){bounds=this.adjustBounds(bounds);var extent=[bounds.left,bounds.bottom,bounds.right,bounds.top];var imageSize=this.getImageSize();var url=this.getFullRequestString({mapext:extent,imgext:extent,map_size:[imageSize.w,imageSize.h],imgx:imageSize.w/2,imgy:imageSize.h/2,imgxy:[imageSize.w,imageSize.h]});return url;},getFullRequestString:function(newParams,altUrl){var url=(altUrl==null)?this.url:altUrl;var allParams=OpenLayers.Util.extend({},this.params);allParams=OpenLayers.Util.extend(allParams,newParams);var paramsString=OpenLayers.Util.getParameterString(allParams);if(url instanceof Array){url=this.selectUrl(paramsString,url);}
 var urlParams=OpenLayers.Util.upperCaseObject(OpenLayers.Util.getParameters(url));for(var key in allParams){if(key.toUpperCase()in urlParams){delete allParams[key];}}
 paramsString=OpenLayers.Util.getParameterString(allParams);var requestString=url;paramsString=paramsString.replace(/,/g,"+");if(paramsString!=""){var lastServerChar=url.charAt(url.length-1);if((lastServerChar=="&")||(lastServerChar=="?")){requestString+=paramsString;}else{if(url.indexOf('?')==-1){requestString+='?'+paramsString;}else{requestString+='&'+paramsString;}}}
 return requestString;},CLASS_NAME:"OpenLayers.Layer.MapServer"});OpenLayers.Layer.WMS=OpenLayers.Class(OpenLayers.Layer.Grid,{DEFAULT_PARAMS:{service:"WMS",version:"1.1.1",request:"GetMap",styles:"",exceptions:"application/vnd.ogc.se_inimage",format:"image/jpeg"},reproject:false,isBaseLayer:true,encodeBBOX:false,initialize:function(name,url,params,options){var newArguments=[];params=OpenLayers.Util.upperCaseObject(params);newArguments.push(name,url,params,options);OpenLayers.Layer.Grid.prototype.initialize.apply(this,newArguments);OpenLayers.Util.applyDefaults(this.params,OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS));if(this.params.TRANSPARENT&&this.params.TRANSPARENT.toString().toLowerCase()=="true"){if((options==null)||(!options.isBaseLayer)){this.isBaseLayer=false;}
 if(this.params.FORMAT=="image/jpeg"){this.params.FORMAT=OpenLayers.Util.alphaHack()?"image/gif":"image/png";}}},destroy:function(){OpenLayers.Layer.Grid.prototype.destroy.apply(this,arguments);},clone:function(obj){if(obj==null){obj=new OpenLayers.Layer.WMS(this.name,this.url,this.params,this.options);}
-obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var imageSize=this.getImageSize();var newParams={'BBOX':this.encodeBBOX?bounds.toBBOX():bounds.toArray(),'WIDTH':imageSize.w,'HEIGHT':imageSize.h};var requestString=this.getFullRequestString(newParams);return requestString;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},mergeNewParams:function(newParams){var upperParams=OpenLayers.Util.upperCaseObject(newParams);var newArguments=[upperParams];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,newArguments);},getFullRequestString:function(newParams,altUrl){var projectionCode=this.map.getProjection();this.params.SRS=(projectionCode=="none")?null:projectionCode;return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,arguments);},CLASS_NAME:"OpenLayers.Layer.WMS"});
\ No newline at end of file
+obj=OpenLayers.Layer.Grid.prototype.clone.apply(this,[obj]);return obj;},getURL:function(bounds){bounds=this.adjustBounds(bounds);var imageSize=this.getImageSize();var newParams={'BBOX':this.encodeBBOX?bounds.toBBOX():bounds.toArray(),'WIDTH':imageSize.w,'HEIGHT':imageSize.h};var requestString=this.getFullRequestString(newParams);return requestString;},addTile:function(bounds,position){return new OpenLayers.Tile.Image(this,position,bounds,null,this.tileSize);},mergeNewParams:function(newParams){var upperParams=OpenLayers.Util.upperCaseObject(newParams);var newArguments=[upperParams];return OpenLayers.Layer.Grid.prototype.mergeNewParams.apply(this,newArguments);},getFullRequestString:function(newParams,altUrl){var projectionCode=this.map.getProjection();this.params.SRS=(projectionCode=="none")?null:projectionCode;return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(this,arguments);},CLASS_NAME:"OpenLayers.Layer.WMS"});OpenLayers.StyleMap=OpenLayers.Class({styles:null,extendDefault:true,initialize:function(style,options){this.styles={"default":new OpenLayers.Style(OpenLayers.Feature.Vector.style["default"]),"select":new OpenLayers.Style(OpenLayers.Feature.Vector.style["select"]),"temporary":new OpenLayers.Style(OpenLayers.Feature.Vector.style["temporary"])};if(style instanceof OpenLayers.Style){this.styles["default"]=style;this.styles["select"]=style;this.styles["temporary"]=style;}else if(typeof style=="object"){for(var key in style){if(style[key]instanceof OpenLayers.Style){this.styles[key]=style[key];}else if(typeof style[key]=="object"){this.styles[key]=new OpenLayers.Style(style[key]);}else{this.styles["default"]=new OpenLayers.Style(style);this.styles["select"]=new OpenLayers.Style(style);this.styles["temporary"]=new OpenLayers.Style(style);break;}}}
+OpenLayers.Util.extend(this,options);},destroy:function(){for(var key in this.styles){this.styles[key].destroy();}
+this.styles=null;},createSymbolizer:function(feature,intent){if(!feature){feature=new OpenLayers.Feature.Vector();}
+if(!this.styles[intent]){intent="default";}
+feature.renderIntent=intent;var defaultSymbolizer={};if(this.extendDefault&&intent!="default"){defaultSymbolizer=this.styles["default"].createSymbolizer(feature);}
+return OpenLayers.Util.extend(defaultSymbolizer,this.styles[intent].createSymbolizer(feature));},addUniqueValueRules:function(renderIntent,property,symbolizers,context){var rules=[];for(var value in symbolizers){rules.push(new OpenLayers.Rule({symbolizer:symbolizers[value],context:context,filter:new OpenLayers.Filter.Comparison({type:OpenLayers.Filter.Comparison.EQUAL_TO,property:property,value:value})}));}
+this.styles[renderIntent].addRules(rules);},CLASS_NAME:"OpenLayers.StyleMap"});OpenLayers.Geometry.Collection=OpenLayers.Class(OpenLayers.Geometry,{components:null,componentTypes:null,initialize:function(components){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.components=[];if(components!=null){this.addComponents(components);}},destroy:function(){this.components.length=0;this.components=null;},clone:function(){var geometry=eval("new "+this.CLASS_NAME+"()");for(var i=0,len=this.components.length;i<len;i++){geometry.addComponent(this.components[i].clone());}
+OpenLayers.Util.applyDefaults(geometry,this);return geometry;},getComponentsString:function(){var strings=[];for(var i=0,len=this.components.length;i<len;i++){strings.push(this.components[i].toShortString());}
+return strings.join(",");},calculateBounds:function(){this.bounds=null;if(this.components&&this.components.length>0){this.setBounds(this.components[0].getBounds());for(var i=1,len=this.components.length;i<len;i++){this.extendBounds(this.components[i].getBounds());}}},addComponents:function(components){if(!(components instanceof Array)){components=[components];}
+for(var i=0,len=components.length;i<len;i++){this.addComponent(components[i]);}},addComponent:function(component,index){var added=false;if(component){if(this.componentTypes==null||(OpenLayers.Util.indexOf(this.componentTypes,component.CLASS_NAME)>-1)){if(index!=null&&(index<this.components.length)){var components1=this.components.slice(0,index);var components2=this.components.slice(index,this.components.length);components1.push(component);this.components=components1.concat(components2);}else{this.components.push(component);}
+component.parent=this;this.clearBounds();added=true;}}
+return added;},removeComponents:function(components){if(!(components instanceof Array)){components=[components];}
+for(var i=components.length-1;i>=0;--i){this.removeComponent(components[i]);}},removeComponent:function(component){OpenLayers.Util.removeItem(this.components,component);this.clearBounds();},getLength:function(){var length=0.0;for(var i=0,len=this.components.length;i<len;i++){length+=this.components[i].getLength();}
+return length;},getArea:function(){var area=0.0;for(var i=0,len=this.components.length;i<len;i++){area+=this.components[i].getArea();}
+return area;},move:function(x,y){for(var i=0,len=this.components.length;i<len;i++){this.components[i].move(x,y);}},rotate:function(angle,origin){for(var i=0,len=this.components.length;i<len;++i){this.components[i].rotate(angle,origin);}},resize:function(scale,origin,ratio){for(var i=0;i<this.components.length;++i){this.components[i].resize(scale,origin,ratio);}},equals:function(geometry){var equivalent=true;if(!geometry||!geometry.CLASS_NAME||(this.CLASS_NAME!=geometry.CLASS_NAME)){equivalent=false;}else if(!(geometry.components instanceof Array)||(geometry.components.length!=this.components.length)){equivalent=false;}else{for(var i=0,len=this.components.length;i<len;++i){if(!this.components[i].equals(geometry.components[i])){equivalent=false;break;}}}
+return equivalent;},transform:function(source,dest){if(source&&dest){for(var i=0,len=this.components.length;i<len;i++){var component=this.components[i];component.transform(source,dest);}
+this.bounds=null;}
+return this;},intersects:function(geometry){var intersect=false;for(var i=0,len=this.components.length;i<len;++i){intersect=geometry.intersects(this.components[i]);if(intersect){break;}}
+return intersect;},CLASS_NAME:"OpenLayers.Geometry.Collection"});OpenLayers.Geometry.Point=OpenLayers.Class(OpenLayers.Geometry,{x:null,y:null,initialize:function(x,y){OpenLayers.Geometry.prototype.initialize.apply(this,arguments);this.x=parseFloat(x);this.y=parseFloat(y);},clone:function(obj){if(obj==null){obj=new OpenLayers.Geometry.Point(this.x,this.y);}
+OpenLayers.Util.applyDefaults(obj,this);return obj;},calculateBounds:function(){this.bounds=new OpenLayers.Bounds(this.x,this.y,this.x,this.y);},distanceTo:function(point){var distance=0.0;if((this.x!=null)&&(this.y!=null)&&(point!=null)&&(point.x!=null)&&(point.y!=null)){var dx2=Math.pow(this.x-point.x,2);var dy2=Math.pow(this.y-point.y,2);distance=Math.sqrt(dx2+dy2);}
+return distance;},equals:function(geom){var equals=false;if(geom!=null){equals=((this.x==geom.x&&this.y==geom.y)||(isNaN(this.x)&&isNaN(this.y)&&isNaN(geom.x)&&isNaN(geom.y)));}
+return equals;},toShortString:function(){return(this.x+", "+this.y);},move:function(x,y){this.x=this.x+x;this.y=this.y+y;this.clearBounds();},rotate:function(angle,origin){angle*=Math.PI/180;var radius=this.distanceTo(origin);var theta=angle+Math.atan2(this.y-origin.y,this.x-origin.x);this.x=origin.x+(radius*Math.cos(theta));this.y=origin.y+(radius*Math.sin(theta));this.clearBounds();},resize:function(scale,origin,ratio){ratio=(ratio==undefined)?1:ratio;this.x=origin.x+(scale*ratio*(this.x-origin.x));this.y=origin.y+(scale*(this.y-origin.y));this.clearBounds();},intersects:function(geometry){var intersect=false;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){intersect=this.equals(geometry);}else{intersect=geometry.intersects(this);}
+return intersect;},transform:function(source,dest){if((source&&dest)){OpenLayers.Projection.transform(this,source,dest);this.bounds=null;}
+return this;},CLASS_NAME:"OpenLayers.Geometry.Point"});OpenLayers.Layer.Vector=OpenLayers.Class(OpenLayers.Layer,{EVENT_TYPES:["beforefeatureadded","featureadded","featuresadded","beforefeatureremoved","featureremoved","featuresremoved","beforefeatureselected","featureselected","featureunselected","beforefeaturemodified","featuremodified","afterfeaturemodified"],isBaseLayer:false,isFixed:false,isVector:true,features:null,selectedFeatures:null,unrenderedFeatures:null,reportError:true,style:null,styleMap:null,strategies:null,protocol:null,renderers:['SVG','VML','Canvas'],renderer:null,rendererOptions:null,geometryType:null,drawn:false,initialize:function(name,options){this.EVENT_TYPES=OpenLayers.Layer.Vector.prototype.EVENT_TYPES.concat(OpenLayers.Layer.prototype.EVENT_TYPES);OpenLayers.Layer.prototype.initialize.apply(this,arguments);if(!this.renderer||!this.renderer.supported()){this.assignRenderer();}
+if(!this.renderer||!this.renderer.supported()){this.renderer=null;this.displayError();}
+if(!this.styleMap){this.styleMap=new OpenLayers.StyleMap();}
+this.features=[];this.selectedFeatures=[];this.unrenderedFeatures={};if(this.strategies){for(var i=0,len=this.strategies.length;i<len;i++){this.strategies[i].setLayer(this);}}},destroy:function(){if(this.strategies){var strategy,i,len;for(i=0,len=this.strategies.length;i<len;i++){strategy=this.strategies[i];if(strategy.autoDestroy){strategy.destroy();}}
+this.strategies=null;}
+if(this.protocol){if(this.protocol.autoDestroy){this.protocol.destroy();}
+this.protocol=null;}
+this.destroyFeatures();this.features=null;this.selectedFeatures=null;this.unrenderedFeatures=null;if(this.renderer){this.renderer.destroy();}
+this.renderer=null;this.geometryType=null;this.drawn=null;OpenLayers.Layer.prototype.destroy.apply(this,arguments);},assignRenderer:function(){for(var i=0,len=this.renderers.length;i<this.renderers.length;i++){var rendererClass=OpenLayers.Renderer[this.renderers[i]];if(rendererClass&&rendererClass.prototype.supported()){this.renderer=new rendererClass(this.div,this.rendererOptions);break;}}},displayError:function(){if(this.reportError){OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported",{'renderers':this.renderers.join("\n")}));}},setMap:function(map){OpenLayers.Layer.prototype.setMap.apply(this,arguments);if(!this.renderer){this.map.removeLayer(this);}else{this.renderer.map=this.map;this.renderer.setSize(this.map.getSize());}
+if(this.strategies){var strategy,i,len;for(i=0,len=this.strategies.length;i<len;i++){strategy=this.strategies[i];if(strategy.autoActivate){strategy.activate();}}}},removeMap:function(map){if(this.strategies){var strategy,i,len;for(i=0,len=this.strategies.length;i<len;i++){strategy=this.strategies[i];if(strategy.autoActivate){strategy.deactivate();}}}},onMapResize:function(){OpenLayers.Layer.prototype.onMapResize.apply(this,arguments);this.renderer.setSize(this.map.getSize());},moveTo:function(bounds,zoomChanged,dragging){OpenLayers.Layer.prototype.moveTo.apply(this,arguments);var coordSysUnchanged=true;if(!dragging){this.renderer.root.style.visibility="hidden";this.div.style.left=-parseInt(this.map.layerContainerDiv.style.left)+"px";this.div.style.top=-parseInt(this.map.layerContainerDiv.style.top)+"px";var extent=this.map.getExtent();coordSysUnchanged=this.renderer.setExtent(extent,zoomChanged);this.renderer.root.style.visibility="visible";if(navigator.userAgent.toLowerCase().indexOf("gecko")!=-1){this.div.scrollLeft=this.div.scrollLeft;}
+if(!zoomChanged&&coordSysUnchanged){var unrenderedFeatures={};for(var i in this.unrenderedFeatures){var feature=this.unrenderedFeatures[i];if(!this.drawFeature(feature)){unrenderedFeatures[i]=feature;}}
+this.unrenderedFeatures=unrenderedFeatures;}}
+if(!this.drawn||zoomChanged||!coordSysUnchanged){this.unrenderedFeatures={};this.drawn=true;var feature;for(var i=0,len=this.features.length;i<len;i++){if(i!=(this.features.length-1)){this.renderer.locked=true;}else{this.renderer.locked=false;}
+feature=this.features[i];if(!this.drawFeature(feature)){this.unrenderedFeatures[feature.id]=feature;};}}},addFeatures:function(features,options){if(!(features instanceof Array)){features=[features];}
+var notify=!options||!options.silent;for(var i=0,len=features.length;i<len;i++){if(i!=(features.length-1)){this.renderer.locked=true;}else{this.renderer.locked=false;}
+var feature=features[i];if(this.geometryType&&!(feature.geometry instanceof this.geometryType)){var throwStr=OpenLayers.i18n('componentShouldBe',{'geomType':this.geometryType.prototype.CLASS_NAME});throw throwStr;}
+this.features.push(feature);feature.layer=this;if(!feature.style&&this.style){feature.style=OpenLayers.Util.extend({},this.style);}
+if(notify){this.events.triggerEvent("beforefeatureadded",{feature:feature});this.preFeatureInsert(feature);}
+if(this.drawn){if(!this.drawFeature(feature)){this.unrenderedFeatures[feature.id]=feature;}}
+if(notify){this.events.triggerEvent("featureadded",{feature:feature});this.onFeatureInsert(feature);}}
+if(notify){this.events.triggerEvent("featuresadded",{features:features});}},removeFeatures:function(features,options){if(!(features instanceof Array)){features=[features];}
+if(features.length<=0){return;}
+var notify=!options||!options.silent;for(var i=features.length-1;i>=0;i--){if(i!=0&&features[i-1].geometry){this.renderer.locked=true;}else{this.renderer.locked=false;}
+var feature=features[i];delete this.unrenderedFeatures[feature.id];if(notify){this.events.triggerEvent("beforefeatureremoved",{feature:feature});}
+this.features=OpenLayers.Util.removeItem(this.features,feature);feature.layer=null;if(feature.geometry){this.renderer.eraseGeometry(feature.geometry);}
+if(OpenLayers.Util.indexOf(this.selectedFeatures,feature)!=-1){OpenLayers.Util.removeItem(this.selectedFeatures,feature);}
+if(notify){this.events.triggerEvent("featureremoved",{feature:feature});}}
+if(notify){this.events.triggerEvent("featuresremoved",{features:features});}},destroyFeatures:function(features,options){var all=(features==undefined);if(all){features=this.features;}
+this.removeFeatures(features,options);for(var i=0;i<features.length;i++){features[i].destroy();}},drawFeature:function(feature,style){if(typeof style!="object"){var renderIntent=typeof style=="string"?style:feature.renderIntent;style=feature.style||this.style;if(!style){style=this.styleMap.createSymbolizer(feature,renderIntent);}}
+return this.renderer.drawFeature(feature,style);},eraseFeatures:function(features){this.renderer.eraseFeatures(features);},getFeatureFromEvent:function(evt){if(!this.renderer){OpenLayers.Console.error(OpenLayers.i18n("getFeatureError"));return null;}
+var featureId=this.renderer.getFeatureIdFromEvent(evt);return this.getFeatureById(featureId);},getFeatureById:function(featureId){var feature=null;for(var i=0,len=this.features.length;i<len;++i){if(this.features[i].id==featureId){feature=this.features[i];break;}}
+return feature;},onFeatureInsert:function(feature){},preFeatureInsert:function(feature){},getDataExtent:function(){var maxExtent=null;if(this.features&&(this.features.length>0)){var maxExtent=this.features[0].geometry.getBounds();for(var i=0,len=this.features.length;i<len;i++){maxExtent.extend(this.features[i].geometry.getBounds());}}
+return maxExtent;},CLASS_NAME:"OpenLayers.Layer.Vector"});OpenLayers.Geometry.MultiPoint=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.Point"],initialize:function(components){OpenLayers.Geometry.Collection.prototype.initialize.apply(this,arguments);},addPoint:function(point,index){this.addComponent(point,index);},removePoint:function(point){this.removeComponent(point);},CLASS_NAME:"OpenLayers.Geometry.MultiPoint"});OpenLayers.Geometry.Polygon=OpenLayers.Class(OpenLayers.Geometry.Collection,{componentTypes:["OpenLayers.Geometry.LinearRing"],initialize:function(components){OpenLayers.Geometry.Collection.prototype.initialize.apply(this,arguments);},getArea:function(){var area=0.0;if(this.components&&(this.components.length>0)){area+=Math.abs(this.components[0].getArea());for(var i=1,len=this.components.length;i<len;i++){area-=Math.abs(this.components[i].getArea());}}
+return area;},containsPoint:function(point){var numRings=this.components.length;var contained=false;if(numRings>0){contained=this.components[0].containsPoint(point);if(contained!==1){if(contained&&numRings>1){var hole;for(var i=1;i<numRings;++i){hole=this.components[i].containsPoint(point);if(hole){if(hole===1){contained=1;}else{contained=false;}
+break;}}}}}
+return contained;},intersects:function(geometry){var intersect=false;var i,len;if(geometry.CLASS_NAME=="OpenLayers.Geometry.Point"){intersect=this.containsPoint(geometry);}else if(geometry.CLASS_NAME=="OpenLayers.Geometry.LineString"||geometry.CLASS_NAME=="OpenLayers.Geometry.LinearRing"){for(i=0,len=this.components.length;i<len;++i){intersect=geometry.intersects(this.components[i]);if(intersect){break;}}
+if(!intersect){for(i=0,len=geometry.components.length;i<len;++i){intersect=this.containsPoint(geometry.components[i]);if(intersect){break;}}}}else{for(i=0,len=geometry.components.length;i<len;++i){intersect=this.intersects(geometry.components[i]);if(intersect){break;}}}
+if(!intersect&&geometry.CLASS_NAME=="OpenLayers.Geometry.Polygon"){var ring=this.components[0];for(i=0,len=ring.components.length;i<len;++i){intersect=geometry.containsPoint(ring.components[i]);if(intersect){break;}}}
+return intersect;},CLASS_NAME:"OpenLayers.Geometry.Polygon"});OpenLayers.Geometry.Polygon.createRegularPolygon=function(origin,radius,sides,rotation){var angle=Math.PI*((1/sides)-(1/2));if(rotation){angle+=(rotation/180)*Math.PI;}
+var rotatedAngle,x,y;var points=[];for(var i=0;i<sides;++i){rotatedAngle=angle+(i*2*Math.PI/sides);x=origin.x+(radius*Math.cos(rotatedAngle));y=origin.y+(radius*Math.sin(rotatedAngle));points.push(new OpenLayers.Geometry.Point(x,y));}
+var ring=new OpenLayers.Geometry.LinearRing(points);return new OpenLayers.Geometry.Polygon([ring]);};OpenLayers.Handler.Point=OpenLayers.Class(OpenLayers.Handler,{point:null,layer:null,drawing:false,mouseDown:false,lastDown:null,lastUp:null,initialize:function(control,callbacks,options){this.style=OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'],{});OpenLayers.Handler.prototype.initialize.apply(this,arguments);},activate:function(){if(!OpenLayers.Handler.prototype.activate.apply(this,arguments)){return false;}
+var options={displayInLayerSwitcher:false,calculateInRange:function(){return true;}};this.layer=new OpenLayers.Layer.Vector(this.CLASS_NAME,options);this.map.addLayer(this.layer);return true;},createFeature:function(){this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point());},deactivate:function(){if(!OpenLayers.Handler.prototype.deactivate.apply(this,arguments)){return false;}
+if(this.drawing){this.cancel();}
+if(this.layer.map!=null){this.layer.destroy(false);}
+this.layer=null;return true;},destroyFeature:function(){if(this.point){this.point.destroy();}
+this.point=null;},finalize:function(){this.layer.renderer.clear();this.drawing=false;this.mouseDown=false;this.lastDown=null;this.lastUp=null;this.callback("done",[this.geometryClone()]);this.destroyFeature();},cancel:function(){this.layer.renderer.clear();this.drawing=false;this.mouseDown=false;this.lastDown=null;this.lastUp=null;this.callback("cancel",[this.geometryClone()]);this.destroyFeature();},click:function(evt){OpenLayers.Event.stop(evt);return false;},dblclick:function(evt){OpenLayers.Event.stop(evt);return false;},drawFeature:function(){this.layer.drawFeature(this.point,this.style);},geometryClone:function(){return this.point.geometry.clone();},mousedown:function(evt){if(!this.checkModifiers(evt)){return true;}
+if(this.lastDown&&this.lastDown.equals(evt.xy)){return true;}
+if(this.lastDown==null){this.createFeature();}
+this.lastDown=evt.xy;this.drawing=true;var lonlat=this.map.getLonLatFromPixel(evt.xy);this.point.geometry.x=lonlat.lon;this.point.geometry.y=lonlat.lat;this.drawFeature();return false;},mousemove:function(evt){if(this.drawing){var lonlat=this.map.getLonLatFromPixel(evt.xy);this.point.geometry.x=lonlat.lon;this.point.geometry.y=lonlat.lat;this.point.geometry.clearBounds();this.drawFeature();}
+return true;},mouseup:function(evt){if(this.drawing){this.finalize();return false;}else{return true;}},CLASS_NAME:"OpenLayers.Handler.Point"});OpenLayers.Geometry.Curve=OpenLayers.Class(OpenLayers.Geometry.MultiPoint,{componentTypes:["OpenLayers.Geometry.Point"],initialize:function(points){OpenLayers.Geometry.MultiPoint.prototype.initialize.apply(this,arguments);},getLength:function(){var length=0.0;if(this.components&&(this.components.length>1)){for(var i=1,len=this.components.length;i<len;i++){length+=this.components[i-1].distanceTo(this.components[i]);}}
+return length;},CLASS_NAME:"OpenLayers.Geometry.Curve"});OpenLayers.Geometry.LineString=OpenLayers.Class(OpenLayers.Geometry.Curve,{initialize:function(points){OpenLayers.Geometry.Curve.prototype.initialize.apply(this,arguments);},removeComponent:function(point){if(this.components&&(this.components.length>2)){OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,arguments);}},intersects:function(geometry){var intersect=false;var type=geometry.CLASS_NAME;if(type=="OpenLayers.Geometry.LineString"||type=="OpenLayers.Geometry.LinearRing"||type=="OpenLayers.Geometry.Point"){var segs1=this.getSortedSegments();var segs2;if(type=="OpenLayers.Geometry.Point"){segs2=[{x1:geometry.x,y1:geometry.y,x2:geometry.x,y2:geometry.y}];}else{segs2=geometry.getSortedSegments();}
+var seg1,seg1x1,seg1x2,seg1y1,seg1y2,seg2,seg2y1,seg2y2;outer:for(var i=0,len=segs1.length;i<len;++i){seg1=segs1[i];seg1x1=seg1.x1;seg1x2=seg1.x2;seg1y1=seg1.y1;seg1y2=seg1.y2;inner:for(var j=0,jlen=segs2.length;j<jlen;++j){seg2=segs2[j];if(seg2.x1>seg1x2){break;}
+if(seg2.x2<seg1x1){continue;}
+seg2y1=seg2.y1;seg2y2=seg2.y2;if(Math.min(seg2y1,seg2y2)>Math.max(seg1y1,seg1y2)){continue;}
+if(Math.max(seg2y1,seg2y2)<Math.min(seg1y1,seg1y2)){continue;}
+if(OpenLayers.Geometry.segmentsIntersect(seg1,seg2)){intersect=true;break outer;}}}}else{intersect=geometry.intersects(this);}
+return intersect;},getSortedSegments:function(){var numSeg=this.components.length-1;var segments=new Array(numSeg);for(var i=0;i<numSeg;++i){point1=this.components[i];point2=this.components[i+1];if(point1.x<point2.x){segments[i]={x1:point1.x,y1:point1.y,x2:point2.x,y2:point2.y};}else{segments[i]={x1:point2.x,y1:point2.y,x2:point1.x,y2:point1.y};}}
+function byX1(seg1,seg2){return seg1.x1-seg2.x1;}
+return segments.sort(byX1);},CLASS_NAME:"OpenLayers.Geometry.LineString"});OpenLayers.Handler.Path=OpenLayers.Class(OpenLayers.Handler.Point,{line:null,freehand:false,freehandToggle:'shiftKey',initialize:function(control,callbacks,options){OpenLayers.Handler.Point.prototype.initialize.apply(this,arguments);},createFeature:function(){this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LineString());this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point());},destroyFeature:function(){OpenLayers.Handler.Point.prototype.destroyFeature.apply(this);if(this.line){this.line.destroy();}
+this.line=null;},addPoint:function(){this.line.geometry.addComponent(this.point.geometry.clone(),this.line.geometry.components.length);this.callback("point",[this.point.geometry]);},freehandMode:function(evt){return(this.freehandToggle&&evt[this.freehandToggle])?!this.freehand:this.freehand;},modifyFeature:function(){var index=this.line.geometry.components.length-1;this.line.geometry.components[index].x=this.point.geometry.x;this.line.geometry.components[index].y=this.point.geometry.y;this.line.geometry.components[index].clearBounds();},drawFeature:function(){this.layer.drawFeature(this.line,this.style);this.layer.drawFeature(this.point,this.style);},geometryClone:function(){return this.line.geometry.clone();},mousedown:function(evt){if(this.lastDown&&this.lastDown.equals(evt.xy)){return false;}
+if(this.lastDown==null){this.createFeature();}
+this.mouseDown=true;this.lastDown=evt.xy;var lonlat=this.control.map.getLonLatFromPixel(evt.xy);this.point.geometry.x=lonlat.lon;this.point.geometry.y=lonlat.lat;if((this.lastUp==null)||!this.lastUp.equals(evt.xy)){this.addPoint();}
+this.drawFeature();this.drawing=true;return false;},mousemove:function(evt){if(this.drawing){var lonlat=this.map.getLonLatFromPixel(evt.xy);this.point.geometry.x=lonlat.lon;this.point.geometry.y=lonlat.lat;this.point.geometry.clearBounds();if(this.mouseDown&&this.freehandMode(evt)){this.addPoint();}else{this.modifyFeature();}
+this.drawFeature();}
+return true;},mouseup:function(evt){this.mouseDown=false;if(this.drawing){if(this.freehandMode(evt)){this.finalize();}else{if(this.lastUp==null){this.addPoint();}
+this.lastUp=evt.xy;}
+return false;}
+return true;},dblclick:function(evt){if(!this.freehandMode(evt)){var index=this.line.geometry.components.length-1;this.line.geometry.removeComponent(this.line.geometry.components[index]);this.finalize();}
+return false;},CLASS_NAME:"OpenLayers.Handler.Path"});OpenLayers.Handler.Polygon=OpenLayers.Class(OpenLayers.Handler.Path,{polygon:null,initialize:function(control,callbacks,options){OpenLayers.Handler.Path.prototype.initialize.apply(this,arguments);},createFeature:function(){this.polygon=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Polygon());this.line=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.LinearRing());this.polygon.geometry.addComponent(this.line.geometry);this.point=new OpenLayers.Feature.Vector(new OpenLayers.Geometry.Point());},destroyFeature:function(){OpenLayers.Handler.Path.prototype.destroyFeature.apply(this);if(this.polygon){this.polygon.destroy();}
+this.polygon=null;},modifyFeature:function(){var index=this.line.geometry.components.length-2;this.line.geometry.components[index].x=this.point.geometry.x;this.line.geometry.components[index].y=this.point.geometry.y;this.line.geometry.components[index].clearBounds();},drawFeature:function(){this.layer.drawFeature(this.polygon,this.style);this.layer.drawFeature(this.point,this.style);},geometryClone:function(){return this.polygon.geometry.clone();},dblclick:function(evt){if(!this.freehandMode(evt)){var index=this.line.geometry.components.length-2;this.line.geometry.removeComponent(this.line.geometry.components[index]);this.finalize();}
+return false;},CLASS_NAME:"OpenLayers.Handler.Polygon"});
\ No newline at end of file

Modified: trunk/lib/OpenLayers/OpenLayersUncompressed.js
===================================================================
--- trunk/lib/OpenLayers/OpenLayersUncompressed.js	2008-09-05 14:31:06 UTC (rev 1498)
+++ trunk/lib/OpenLayers/OpenLayersUncompressed.js	2008-09-05 14:39:19 UTC (rev 1499)
@@ -43,6 +43,15 @@
 *
 **/
 
+/**
+ * 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/SingleFile.js
    ====================================================================== */
@@ -102,7 +111,7 @@
             var scriptName = OpenLayers._scriptName;
          
             var scripts = document.getElementsByTagName('script');
-            for (var i = 0; i < scripts.length; i++) {
+            for (var i=0, len=scripts.length; i<len; i++) {
                 var src = scripts[i].getAttribute('src');
                 if (src) {
                     var index = src.lastIndexOf(scriptName); 
@@ -146,6 +155,8 @@
             "Rico/Corner.js",
             "Rico/Color.js",
             "OpenLayers/Ajax.js",
+            "OpenLayers/Request.js",
+            "OpenLayers/Request/XMLHttpRequest.js",
             "OpenLayers/Events.js",
             "OpenLayers/Projection.js",
             "OpenLayers/Map.js",
@@ -238,8 +249,12 @@
             "OpenLayers/Renderer.js",
             "OpenLayers/Renderer/Elements.js",
             "OpenLayers/Renderer/SVG.js",
+            "OpenLayers/Renderer/Canvas.js",
             "OpenLayers/Renderer/VML.js",
             "OpenLayers/Layer/Vector.js",
+            "OpenLayers/Strategy.js",
+            "OpenLayers/Strategy/Fixed.js",
+            "OpenLayers/Protocol.js",
             "OpenLayers/Layer/PointTrack.js",
             "OpenLayers/Layer/GML.js",
             "OpenLayers/Style.js",
@@ -257,9 +272,14 @@
             "OpenLayers/Format/WFS.js",
             "OpenLayers/Format/WKT.js",
             "OpenLayers/Format/OSM.js",
+            "OpenLayers/Format/GPX.js",
             "OpenLayers/Format/SLD.js",
             "OpenLayers/Format/SLD/v1.js",
             "OpenLayers/Format/SLD/v1_0_0.js",
+            "OpenLayers/Format/SLD/v1.js",
+            "OpenLayers/Format/Filter.js",
+            "OpenLayers/Format/Filter/v1.js",
+            "OpenLayers/Format/Filter/v1_0_0.js",
             "OpenLayers/Format/Text.js",
             "OpenLayers/Format/JSON.js",
             "OpenLayers/Format/GeoJSON.js",
@@ -281,7 +301,7 @@
             var allScriptTags = new Array(jsfiles.length);
         }
         var host = OpenLayers._getScriptLocation() + "lib/";    
-        for (var i = 0; i < jsfiles.length; i++) {
+        for (var i=0, len=jsfiles.length; i<len; i++) {
             if (docWrite) {
                 allScriptTags[i] = "<script src='" + host + jsfiles[i] +
                                    "'></script>"; 
@@ -303,7 +323,7 @@
 /**
  * Constant: VERSION_NUMBER
  */
-OpenLayers.VERSION_NUMBER="$Revision: 6819 $";
+OpenLayers.VERSION_NUMBER="$Revision: 7862 $";
 /* ======================================================================
     OpenLayers/BaseTypes.js
    ====================================================================== */
@@ -394,7 +414,7 @@
     camelize: function(str) {
         var oStringList = str.split('-');
         var camelizedString = oStringList[0];
-        for (var i = 1; i < oStringList.length; i++) {
+        for (var i=1, len=oStringList.length; i<len; i++) {
             var s = oStringList[i];
             camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
         }
@@ -428,7 +448,7 @@
         }
         var tokens = template.split("${");
         var item, last, replacement;
-        for(var i=1; i<tokens.length; i++) {
+        for(var i=1, len=tokens.length; i<len; i++) {
             item = tokens[i];
             last = item.indexOf("}"); 
             if(last > 0) {
@@ -444,6 +464,31 @@
             }
         }
         return tokens.join("");
+    },
+    
+    /**
+     * Property: OpenLayers.String.numberRegEx
+     * Used to test strings as numbers.
+     */
+    numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
+    
+    /**
+     * APIFunction: OpenLayers.String.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);
     }
 
 };
@@ -812,7 +857,7 @@
     };
     var extended = {};
     var parent;
-    for(var i=0; i<arguments.length; ++i) {
+    for(var i=0, len=arguments.length; i<len; ++i) {
         if(typeof arguments[i] == "function") {
             // get the prototype of the superclass
             parent = arguments[i].prototype;
@@ -863,7 +908,7 @@
 OpenLayers.Class.inherit = function () {
     var superClass = arguments[0];
     var proto = new superClass(OpenLayers.Class.isPrototype);
-    for (var i = 1; i < arguments.length; i++) {
+    for (var i=1, len=arguments.length; i<len; i++) {
         if (typeof arguments[i] == "function") {
             var mixin = arguments[i];
             arguments[i] = new mixin(OpenLayers.Class.isPrototype);
@@ -893,7 +938,7 @@
 OpenLayers.Util.getElement = function() {
     var elements = [];
 
-    for (var i = 0; i < arguments.length; i++) {
+    for (var i=0, len=arguments.length; i<len; i++) {
         var element = arguments[i];
         if (typeof element == 'string') {
             element = document.getElementById(element);
@@ -927,7 +972,8 @@
  * {Object} The destination object.
  */
 OpenLayers.Util.extend = function(destination, source) {
-    if(destination && source) {
+    destination = destination || {};
+    if(source) {
         for(var property in source) {
             var value = source[property];
             if(value !== undefined) {
@@ -1012,7 +1058,7 @@
  */
 OpenLayers.Util.indexOf = function(array, obj) {
 
-    for(var i=0; i < array.length; i++) {
+    for(var i=0, len=array.length; i<len; i++) {
         if (array[i] == obj) {
             return i;
         }
@@ -1075,10 +1121,9 @@
  * 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.d
+ * set a particular attribute.
+ * Note - zIndex is NOT set on the resulting div.
  * 
- * Note: zIndex is NOT set
- * 
  * Parameters:
  * id - {String} An identifier for this element.  If no id is
  *               passed an identifier will be created 
@@ -1241,8 +1286,27 @@
  */
 OpenLayers.Util.onImageLoadError = function() {
     this._attempts = (this._attempts) ? (this._attempts + 1) : 1;
-    if(this._attempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
-        this.src = this.src;
+    if (this._attempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) {
+        var urls = this.urls;
+        if (urls && urls instanceof Array && urls.length > 1){
+            var src = this.src.toString();
+            var current_url, k;
+            for (k = 0; current_url = urls[k]; k++){
+                if(src.indexOf(current_url) != -1){
+                    break;
+                }
+            }
+            var guess = Math.floor(urls.length * Math.random())
+            var new_url = urls[guess];
+            k = 0;
+            while(new_url == current_url && k++ < 4){
+                guess = Math.floor(urls.length * Math.random())
+                new_url = urls[guess];
+            }
+            this.src = src.replace(current_url, new_url);
+        } else {
+            this.src = this.src;
+        }
     } else {
         this.style.backgroundColor = OpenLayers.Util.onImageLoadErrorColor;
     }
@@ -1294,8 +1358,8 @@
                                                position, border, sizing, 
                                                opacity) {
 
-    OpenLayers.Util.modifyDOMElement(div, id, px, sz, 
-                                     null, null, null, opacity);
+    OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
+                                     null, null, opacity);
 
     var img = div.childNodes[0];
 
@@ -1306,8 +1370,9 @@
                                      "relative", border);
     
     if (OpenLayers.Util.alphaHack()) {
-
-        div.style.display = "inline-block";
+        if(div.style.display != "none") {
+            div.style.display = "inline-block";
+        }
         if (sizing == null) {
             sizing = "scale";
         }
@@ -1400,7 +1465,7 @@
  *     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
@@ -1453,7 +1518,7 @@
         if (typeof value == 'object' && value.constructor == Array) {
           /* value is an array; encode items and separate with "," */
           var encodedItemArray = [];
-          for (var itemIndex=0; itemIndex<value.length; itemIndex++) {
+          for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
             encodedItemArray.push(encodeURIComponent(value[itemIndex]));
           }
           encodedValue = encodedItemArray.join(",");
@@ -1504,7 +1569,7 @@
 OpenLayers.Util.Try = function() {
     var returnValue = null;
 
-    for (var i = 0; i < arguments.length; i++) {
+    for (var i=0, len=arguments.length; i<len; i++) {
       var lambda = arguments[i];
       try {
         returnValue = lambda();
@@ -1553,7 +1618,7 @@
  */
 OpenLayers.Util._getNodes=function(nodes, tagName) {
     var retArray = [];
-    for (var i=0;i<nodes.length;i++) {
+    for (var i=0, len=nodes.length; i<len; i++) {
         if (nodes[i].nodeName==tagName) {
             retArray.push(nodes[i]);
         }
@@ -1729,7 +1794,7 @@
         
     var parameters = {};
     var pairs = paramsString.split(/[&;]/);
-    for(var i = 0; i < pairs.length; ++i) {
+    for(var i=0, len=pairs.length; i<len; ++i) {
         var keyValue = pairs[i].split('=');
         if (keyValue[0]) {
             var key = decodeURIComponent(keyValue[0]);
@@ -1737,7 +1802,7 @@
 
             //decode individual values
             value = value.split(",");
-            for(var j=0; j < value.length; j++) {
+            for(var j=0, jlen=value.length; j<jlen; j++) {
                 value[j] = decodeURIComponent(value[j]);
             }
 
@@ -1924,12 +1989,7 @@
     while(element) {
 
         if(element == document.body) {
-            // FIXME: IE, when passed 'window' as the forElement, treats it as
-            // equal to document.body, but window.style fails, so getStyle
-            // fails, so we are paranoid and check this here. This check should
-            // probably move into element.getStyle in 2.6.
-            if(child && child.style && 
-               OpenLayers.Element.getStyle(child, 'position') == 'absolute') {
+            if(OpenLayers.Element.getStyle(child, 'position') == 'absolute') {
                 break;
             }
         }
@@ -1999,7 +2059,7 @@
     //compare all keys (host, port, etc)
     for(var key in urlObj1) {
         if (options.test) {
-            alert(key + "\n1:" + urlObj1[key] + "\n2:" + urlObj2[key]);
+            OpenLayers.Console.userError(key + "\n1:" + urlObj1[key] + "\n2:" + urlObj2[key]);
         }
         var val1 = urlObj1[key];
         var val2 = urlObj2[key];
@@ -2232,17 +2292,21 @@
  *     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}
+ *     displayClass - {String} Optional parameter.  A CSS class name(s) string
+ *         to provide the CSS context of the rendered content.
  * 
  * Returns:
  * {OpenLayers.Size}
  */
-OpenLayers.Util.getRenderedDimensions = function(contentHTML, size) {
+OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
     
-    var w = h = null;
+    var w, h;
     
     // create temp container div with restricted size
     var container = document.createElement("div");
@@ -2253,11 +2317,18 @@
     //fix a dimension, if specified.
     if (size) {
         if (size.w) {
-            w = container.style.width = size.w;
+            w = size.w;
+            container.style.width = w + "px";
         } else if (size.h) {
-            h = container.style.height = 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");
@@ -2352,1003 +2423,340 @@
     return scrollbarWidth;
 };
 /* ======================================================================
-    OpenLayers/Ajax.js
+    Rico/Corner.js
    ====================================================================== */
 
-/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
- * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
- * full text of the license. */
-
-
-OpenLayers.ProxyHost = "";
-//OpenLayers.ProxyHost = "examples/proxy.cgi?url=";
-
-/**
- * Ajax reader for OpenLayers
- *
- *  @uri url to do remote XML http get
- *  @param {String} 'get' format params (x=y&a=b...)
- *  @who object to handle callbacks for this request
- *  @complete  the function to be called on success 
- *  @failure  the function to be called on failure
+/*
+ * This file has been edited substantially from the Rico-released
+ * version by the OpenLayers development team.
  *  
- *   example usage from a caller:
+ *  Copyright 2005 Sabre Airline Solutions  
  *  
- *     caps: function(request) {
- *      -blah-  
- *     },
+ *  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
  *  
- *     OpenLayers.loadURL(url,params,this,caps);
+ *         http://www.apache.org/licenses/LICENSE-2.0  
+ *  
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the * License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or
+ * implied. See the License for the specific language governing
+ * permissions * and limitations under the License.
  *
- * Notice the above example does not provide an error handler; a default empty
- * handler is provided which merely logs the error if a failure handler is not 
- * supplied
- *
- */
+ */  
+OpenLayers.Rico = new Object();
+OpenLayers.Rico.Corner = {
 
+    round: function(e, options) {
+        e = OpenLayers.Util.getElement(e);
+        this._setOptions(options);
 
-/** 
-* @param {} request
-*/
-OpenLayers.nullHandler = function(request) {
-    alert(OpenLayers.i18n("unhandledRequest", {'statusText':request.statusText}));
-};
-
-/** 
- * Function: loadURL
- * Background load a document.
- *
- * Parameters:
- * uri - {String} URI of source doc
- * params - {String} Params on get (doesnt seem to work)
- * caller - {Object} object which gets callbacks
- * onComplete - {Function} Optional callback for success.  The callback
- *     will be called with this set to caller and will receive the request
- *     object as an argument.
- * onFailure - {Function} Optional callback for failure.  In the event of
- *     a failure, the callback will be called with this set to caller and will
- *     receive the request object as an argument.
- *
- * Returns:
- * {XMLHttpRequest}  The request object.  To abort loading, call
- *     request.abort().
- */
-OpenLayers.loadURL = function(uri, params, caller,
-                                  onComplete, onFailure) {
-
-    var success = (onComplete) ? OpenLayers.Function.bind(onComplete, caller)
-                                : OpenLayers.nullHandler;
-
-    var failure = (onFailure) ? OpenLayers.Function.bind(onFailure, caller)
-                           : OpenLayers.nullHandler;
-
-    // from prototype.js
-    var request = new OpenLayers.Ajax.Request(
-        uri, 
-        {
-            method: 'get', 
-            parameters: params,
-            onComplete: success, 
-            onFailure: failure
+        var color = this.options.color;
+        if ( this.options.color == "fromElement" ) {
+            color = this._background(e);
         }
-    );
-    return request.transport;
-};
-
-/** 
- * Function: parseXMLString
- * Parse XML into a doc structure
- * 
- * Parameters:
- * text - {String} 
- * 
- * Returns:
- * {?} Parsed AJAX Responsev
- */
-OpenLayers.parseXMLString = function(text) {
-
-    //MS sucks, if the server is bad it dies
-    var index = text.indexOf('<');
-    if (index > 0) {
-        text = text.substring(index);
-    }
-
-    var ajaxResponse = OpenLayers.Util.Try(
-        function() {
-            var xmldom = new ActiveXObject('Microsoft.XMLDOM');
-            xmldom.loadXML(text);
-            return xmldom;
-        },
-        function() {
-            return new DOMParser().parseFromString(text, 'text/xml');
-        },
-        function() {
-            var req = new XMLHttpRequest();
-            req.open("GET", "data:" + "text/xml" +
-                     ";charset=utf-8," + encodeURIComponent(text), false);
-            if (req.overrideMimeType) {
-                req.overrideMimeType("text/xml");
-            }
-            req.send(null);
-            return req.responseXML;
+        var bgColor = this.options.bgColor;
+        if ( this.options.bgColor == "fromParent" ) {
+            bgColor = this._background(e.offsetParent);
         }
-    );
-
-    return ajaxResponse;
-};
-
-
-/**
- * Namespace: OpenLayers.Ajax
- */
-OpenLayers.Ajax = {
-
-    /**
-     * Method: emptyFunction
-     */
-    emptyFunction: function () {},
-
-    /**
-     * Method: getTransport
-     * 
-     * Returns: 
-     * {Object} Transport mechanism for whichever browser we're in, or false if
-     *          none available.
-     */
-    getTransport: function() {
-        return OpenLayers.Util.Try(
-            function() {return new XMLHttpRequest();},
-            function() {return new ActiveXObject('Msxml2.XMLHTTP');},
-            function() {return new ActiveXObject('Microsoft.XMLHTTP');}
-        ) || false;
+        this._roundCornersImpl(e, color, bgColor);
     },
 
-    /**
-     * Property: activeRequestCount
-     * {Integer}
-     */
-    activeRequestCount: 0
-};
+    /**   This is a helper function to change the background
+    *     color of <div> that has had Rico rounded corners added.
+    *
+    *     It seems we cannot just set the background color for the
+    *     outer <div> so each <span> element used to create the
+    *     corners must have its background color set individually.
+    *
+    * @param {DOM} theDiv - A child of the outer <div> that was
+    *                        supplied to the `round` method.
+    *
+    * @param {String} newColor - The new background color to use.
+    */
+    changeColor: function(theDiv, newColor) {
+   
+        theDiv.style.backgroundColor = newColor;
 
-/**
- * Namespace: OpenLayers.Ajax.Responders
- * {Object}
- */
-OpenLayers.Ajax.Responders = {
-  
-    /**
-     * Property: responders
-     * {Array}
-     */
-    responders: [],
-
-    /**
-     * Method: register
-     *  
-     * Parameters:
-     * responderToAdd - {?}
-     */
-    register: function(responderToAdd) {
-        for (var i = 0; i < this.responders.length; i++){
-            if (responderToAdd == this.responders[i]){
-                return;
-            }
-        }
-        this.responders.push(responderToAdd);
-    },
-
-    /**
-     * Method: unregister
-     *  
-     * Parameters:
-     * responderToRemove - {?}
-     */
-    unregister: function(responderToRemove) {
-        OpenLayers.Util.removeItem(this.reponders, responderToRemove);
-    },
-
-    /**
-     * Method: dispatch
-     * 
-     * Parameters:
-     * callback - {?}
-     * request - {?}
-     * transport - {?}
-     */
-    dispatch: function(callback, request, transport) {
-        var responder;
-        for (var i = 0; i < this.responders.length; i++) {
-            responder = this.responders[i];
-     
-            if (responder[callback] && 
-                typeof responder[callback] == 'function') {
-                try {
-                    responder[callback].apply(responder, 
-                                              [request, transport]);
-                } catch (e) {}
-            }
-        }
-    }
-};
-
-OpenLayers.Ajax.Responders.register({
-    /** 
-     * Function: onCreate
-     */
-    onCreate: function() {
-        OpenLayers.Ajax.activeRequestCount++;
-    },
-
-    /**
-     * Function: onComplete
-     */
-     onComplete: function() {
-         OpenLayers.Ajax.activeRequestCount--;
-     }
-});
-
-/**
- * Class: OpenLayers.Ajax.Base
- */
-OpenLayers.Ajax.Base = OpenLayers.Class({
-      
-    /**
-     * Constructor: OpenLayers.Ajax.Base
-     * 
-     * Parameters: 
-     * options - {Object}
-     */
-    initialize: function(options) {
-        this.options = {
-            method:       'post',
-            asynchronous: true,
-            contentType:  'application/xml',
-            parameters:   ''
-        };
-        OpenLayers.Util.extend(this.options, options || {});
+        var spanElements = theDiv.parentNode.getElementsByTagName("span");
         
-        this.options.method = this.options.method.toLowerCase();
-        
-        if (typeof this.options.parameters == 'string') {
-            this.options.parameters = 
-                OpenLayers.Util.getParameters(this.options.parameters);
+        for (var currIdx = 0; currIdx < spanElements.length; currIdx++) {
+            spanElements[currIdx].style.backgroundColor = newColor;
         }
-    }
-});
+    }, 
 
-/**
- * Class: OpenLayers.Ajax.Request
- *
- * Inherit:
- *  - <OpenLayers.Ajax.Base>
- */
-OpenLayers.Ajax.Request = OpenLayers.Class(OpenLayers.Ajax.Base, {
 
-    /**
-     * Property: _complete
-     *
-     * {Boolean}
-     */
-    _complete: false,
-      
-    /**
-     * Constructor: OpenLayers.Ajax.Request
-     * 
-     * Parameters: 
-     * url - {String}
-     * options - {Object}
-     */
-    initialize: function(url, options) {
-        OpenLayers.Ajax.Base.prototype.initialize.apply(this, [options]);
+    /**   This is a helper function to change the background
+    *     opacity of <div> that has had Rico rounded corners added.
+    *
+    *     See changeColor (above) for algorithm explanation
+    *
+    * @param {DOM} theDiv A child of the outer <div> that was
+    *                        supplied to the `round` method.
+    *
+    * @param {int} newOpacity The new opacity to use (0-1).
+    */
+    changeOpacity: function(theDiv, newOpacity) {
+   
+        var mozillaOpacity = newOpacity;
+        var ieOpacity = 'alpha(opacity=' + newOpacity * 100 + ')';
         
-        if (OpenLayers.ProxyHost && OpenLayers.String.startsWith(url, "http")) {
-            url = OpenLayers.ProxyHost + encodeURIComponent(url);
-        }
-        
-        this.transport = OpenLayers.Ajax.getTransport();
-        this.request(url);
-    },
+        theDiv.style.opacity = mozillaOpacity;
+        theDiv.style.filter = ieOpacity;
 
-    /**
-     * Method: request
-     * 
-     * Parameters:
-     * url - {String}
-     */
-    request: function(url) {
-        this.url = url;
-        this.method = this.options.method;
-        var params = OpenLayers.Util.extend({}, this.options.parameters);
+        var spanElements = theDiv.parentNode.getElementsByTagName("span");
         
-        if (this.method != 'get' && this.method != 'post') {
-            // simulate other verbs over post
-            params['_method'] = this.method;
-            this.method = 'post';
+        for (var currIdx = 0; currIdx < spanElements.length; currIdx++) {
+            spanElements[currIdx].style.opacity = mozillaOpacity;
+            spanElements[currIdx].style.filter = ieOpacity;
         }
 
-        this.parameters = params;        
-        
-        if (params = OpenLayers.Util.getParameterString(params)) {
-            // when GET, append parameters to URL
-            if (this.method == 'get') {
-                this.url += ((this.url.indexOf('?') > -1) ? '&' : '?') + params;
-            } else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
-                params += '&_=';
-            }
-        }
-        try {
-            var response = new OpenLayers.Ajax.Response(this);
-            if (this.options.onCreate) {
-                this.options.onCreate(response);
-            }
-            
-            OpenLayers.Ajax.Responders.dispatch('onCreate', 
-                                                this, 
-                                                response);
-    
-            this.transport.open(this.method.toUpperCase(), 
-                                this.url,
-                                this.options.asynchronous);
-    
-            if (this.options.asynchronous) {
-                window.setTimeout(
-                    OpenLayers.Function.bind(this.respondToReadyState, this, 1),
-                    10);
-            }
-            
-            this.transport.onreadystatechange = 
-                OpenLayers.Function.bind(this.onStateChange, this);    
-            this.setRequestHeaders();
-    
-            this.body =  this.method == 'post' ?
-                (this.options.postBody || params) : null;
-            this.transport.send(this.body);
-    
-            // Force Firefox to handle ready state 4 for synchronous requests
-            if (!this.options.asynchronous && 
-                this.transport.overrideMimeType) {
-                this.onStateChange();
-            }
-        } catch (e) {
-            this.dispatchException(e);
-        }
     },
 
-    /**
-     * Method: onStateChange
-     */
-    onStateChange: function() {
-        var readyState = this.transport.readyState;
-        if (readyState > 1 && !((readyState == 4) && this._complete)) {
-            this.respondToReadyState(this.transport.readyState);
-        }
-    },
-     
-    /**
-     * Method: setRequestHeaders
-     */
-    setRequestHeaders: function() {
-        var headers = {
-            'X-Requested-With': 'XMLHttpRequest',
-            'Accept': 'text/javascript, text/html, application/xml, text/xml, */*',
-            'OpenLayers': true
-        };
+    /** this function takes care of redoing the rico cornering
+    *    
+    *    you can't just call updateRicoCorners() again and pass it a 
+    *    new options string. you have to first remove the divs that 
+    *    rico puts on top and below the content div.
+    *
+    * @param {DOM} theDiv - A child of the outer <div> that was
+    *                        supplied to the `round` method.
+    *
+    * @param {Object} options - list of options
+    */
+    reRound: function(theDiv, options) {
 
-        if (this.method == 'post') {
-            headers['Content-type'] = this.options.contentType +
-                (this.options.encoding ? '; charset=' + this.options.encoding : '');
-    
-            /* Force "Connection: close" for older Mozilla browsers to work
-             * around a bug where XMLHttpRequest sends an incorrect
-             * Content-length header. See Mozilla Bugzilla #246651.
-             */
-            if (this.transport.overrideMimeType &&
-                (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) {
-                headers['Connection'] = 'close';
-            }
-        }
-        // user-defined headers
-        if (typeof this.options.requestHeaders == 'object') {    
-            var extras = this.options.requestHeaders;
-            
-            if (typeof extras.push == 'function') {
-                for (var i = 0, length = extras.length; i < length; i += 2) {
-                    headers[extras[i]] = extras[i+1];
-                }
-            } else {
-                for (var i in extras) {
-                    headers[i] = pair[i];
-                }
-            }
-        }
-        
-        for (var name in headers) {
-            this.transport.setRequestHeader(name, headers[name]);
-        }
-    },
-    
-    /**
-     * Method: success
-     *
-     * Returns:
-     * {Boolean} - 
-     */
-    success: function() {
-        var status = this.getStatus();
-        return !status || (status >=200 && status < 300);
-    },
-    
-    /**
-     * Method: getStatus
-     *
-     * Returns:
-     * {Integer} - Status
-     */
-    getStatus: function() {
-        try {
-            return this.transport.status || 0;
-        } catch (e) {
-            return 0;
-        }
-    },
+        var topRico = theDiv.parentNode.childNodes[0];
+        //theDiv would be theDiv.parentNode.childNodes[1]
+        var bottomRico = theDiv.parentNode.childNodes[2];
+       
+        theDiv.parentNode.removeChild(topRico);
+        theDiv.parentNode.removeChild(bottomRico); 
 
-    /**
-     * Method: respondToReadyState
-     *
-     * Parameters:
-     * readyState - {?}
-     */
-    respondToReadyState: function(readyState) {
-        var state = OpenLayers.Ajax.Request.Events[readyState];
-        var response = new OpenLayers.Ajax.Response(this);
-    
-        if (state == 'Complete') {
-            try {
-                this._complete = true;
-                (this.options['on' + response.status] ||
-                    this.options['on' + (this.success() ? 'Success' : 'Failure')] ||
-                    OpenLayers.Ajax.emptyFunction)(response);
-            } catch (e) {
-                this.dispatchException(e);
-            }
-    
-            var contentType = response.getHeader('Content-type');
-        }
-    
-        try {
-            (this.options['on' + state] || 
-             OpenLayers.Ajax.emptyFunction)(response);
-             OpenLayers.Ajax.Responders.dispatch('on' + state, 
-                                                 this, 
-                                                 response);
-        } catch (e) {
-            this.dispatchException(e);
-        }
-    
-        if (state == 'Complete') {
-            // avoid memory leak in MSIE: clean up
-            this.transport.onreadystatechange = OpenLayers.Ajax.emptyFunction;
-        }
-    },
-    
-    /**
-     * Method: getHeader
-     * 
-     * Parameters:
-     * name - {String} Header name
-     *
-     * Returns:
-     * {?} - response header for the given name
-     */
-    getHeader: function(name) {
-        try {
-            return this.transport.getResponseHeader(name);
-        } catch (e) {
-            return null;
-        }
-    },
+        this.round(theDiv.parentNode, options);
+    }, 
 
-    /**
-     * Method: dispatchException
-     * If the optional onException function is set, execute it
-     * and then dispatch the call to any other listener registered
-     * for onException.
-     * 
-     * If no optional onException function is set, we suspect that
-     * the user may have also not used
-     * OpenLayers.Ajax.Responders.register to register a listener
-     * for the onException call.  To make sure that something
-     * gets done with this exception, only dispatch the call if there
-     * are listeners.
-     *
-     * If you explicitly want to swallow exceptions, set
-     * request.options.onException to an empty function (function(){})
-     * or register an empty function with <OpenLayers.Ajax.Responders>
-     * for onException.
-     * 
-     * Parameters:
-     * exception - {?}
-     */
-    dispatchException: function(exception) {
-        var handler = this.options.onException;
-        if(handler) {
-            // call options.onException and alert any other listeners
-            handler(this, exception);
-            OpenLayers.Ajax.Responders.dispatch('onException', this, exception);
-        } else {
-            // check if there are any other listeners
-            var listener = false;
-            var responders = OpenLayers.Ajax.Responders.responders;
-            for (var i = 0; i < responders.length; i++) {
-                if(responders[i].onException) {
-                    listener = true;
-                    break;
-                }
-            }
-            if(listener) {
-                // call all listeners
-                OpenLayers.Ajax.Responders.dispatch('onException', this, exception);
-            } else {
-                // let the exception through
-                throw exception;
-            }
-        }
-    }
-});
+   _roundCornersImpl: function(e, color, bgColor) {
+      if(this.options.border) {
+         this._renderBorder(e,bgColor);
+      }
+      if(this._isTopRounded()) {
+         this._roundTopCorners(e,color,bgColor);
+      }
+      if(this._isBottomRounded()) {
+         this._roundBottomCorners(e,color,bgColor);
+      }
+   },
 
-/** 
- * Property: Events
- * {Array(String)}
- */
-OpenLayers.Ajax.Request.Events =
-  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+   _renderBorder: function(el,bgColor) {
+      var borderValue = "1px solid " + this._borderColor(bgColor);
+      var borderL = "border-left: "  + borderValue;
+      var borderR = "border-right: " + borderValue;
+      var style   = "style='" + borderL + ";" + borderR +  "'";
+      el.innerHTML = "<div " + style + ">" + el.innerHTML + "</div>";
+   },
 
-/**
- * Class: OpenLayers.Ajax.Response
- */
-OpenLayers.Ajax.Response = OpenLayers.Class({
+   _roundTopCorners: function(el, color, bgColor) {
+      var corner = this._createCorner(bgColor);
+      for(var i=0 ; i < this.options.numSlices ; i++ ) {
+         corner.appendChild(this._createCornerSlice(color,bgColor,i,"top"));
+      }
+      el.style.paddingTop = 0;
+      el.insertBefore(corner,el.firstChild);
+   },
 
-    /**
-     * Property: status
-     *
-     * {Integer}
-     */
-    status: 0,
-    
+   _roundBottomCorners: function(el, color, bgColor) {
+      var corner = this._createCorner(bgColor);
+      for(var i=(this.options.numSlices-1) ; i >= 0 ; i-- ) {
+         corner.appendChild(this._createCornerSlice(color,bgColor,i,"bottom"));
+      }
+      el.style.paddingBottom = 0;
+      el.appendChild(corner);
+   },
 
-    /**
-     * Property: statusText
-     *
-     * {String}
-     */
-    statusText: '',
-      
-    /**
-     * Constructor: OpenLayers.Ajax.Response
-     * 
-     * Parameters: 
-     * request - {Object}
-     */
-    initialize: function(request) {
-        this.request = request;
-        var transport = this.transport = request.transport,
-            readyState = this.readyState = transport.readyState;
-        
-        if ((readyState > 2 &&
-            !(!!(window.attachEvent && !window.opera))) ||
-            readyState == 4) {
-            this.status       = this.getStatus();
-            this.statusText   = this.getStatusText();
-            this.responseText = transport.responseText == null ?
-                '' : String(transport.responseText);
-        }
-        
-        if(readyState == 4) {
-            var xml = transport.responseXML;
-            this.responseXML  = xml === undefined ? null : xml;
-        }
-    },
-    
-    /**
-     * Method: getStatus
-     */
-    getStatus: OpenLayers.Ajax.Request.prototype.getStatus,
-    
-    /**
-     * Method: getStatustext
-     *
-     * Returns:
-     * {String} - statusText
-     */
-    getStatusText: function() {
-        try {
-            return this.transport.statusText || '';
-        } catch (e) {
-            return '';
-        }
-    },
-    
-    /**
-     * Method: getHeader
-     */
-    getHeader: OpenLayers.Ajax.Request.prototype.getHeader,
-    
-    /** 
-     * Method: getResponseHeader
-     *
-     * Returns:
-     * {?} - response header for given name
-     */
-    getResponseHeader: function(name) {
-        return this.transport.getResponseHeader(name);
-    }
-});
+   _createCorner: function(bgColor) {
+      var corner = document.createElement("div");
+      corner.style.backgroundColor = (this._isTransparent() ? "transparent" : bgColor);
+      return corner;
+   },
 
+   _createCornerSlice: function(color,bgColor, n, position) {
+      var slice = document.createElement("span");
 
-/**
- * Function: getElementsByTagNameNS
- * 
- * Parameters:
- * parentnode - {?}
- * nsuri - {?}
- * nsprefix - {?}
- * tagname - {?}
- * 
- * Returns:
- * {?}
- */
-OpenLayers.Ajax.getElementsByTagNameNS  = function(parentnode, nsuri, 
-                                                   nsprefix, tagname) {
-    var elem = null;
-    if (parentnode.getElementsByTagNameNS) {
-        elem = parentnode.getElementsByTagNameNS(nsuri, tagname);
-    } else {
-        elem = parentnode.getElementsByTagName(nsprefix + ':' + tagname);
-    }
-    return elem;
-};
+      var inStyle = slice.style;
+      inStyle.backgroundColor = color;
+      inStyle.display  = "block";
+      inStyle.height   = "1px";
+      inStyle.overflow = "hidden";
+      inStyle.fontSize = "1px";
 
+      var borderColor = this._borderColor(color,bgColor);
+      if ( this.options.border && n == 0 ) {
+         inStyle.borderTopStyle    = "solid";
+         inStyle.borderTopWidth    = "1px";
+         inStyle.borderLeftWidth   = "0px";
+         inStyle.borderRightWidth  = "0px";
+         inStyle.borderBottomWidth = "0px";
+         inStyle.height            = "0px"; // assumes css compliant box model
+         inStyle.borderColor       = borderColor;
+      }
+      else if(borderColor) {
+         inStyle.borderColor = borderColor;
+         inStyle.borderStyle = "solid";
+         inStyle.borderWidth = "0px 1px";
+      }
 
-/**
- * Function: serializeXMLToString
- * Wrapper function around XMLSerializer, which doesn't exist/work in
- *     IE/Safari. We need to come up with a way to serialize in those browser:
- *     for now, these browsers will just fail. #535, #536
- *
- * Parameters: 
- * xmldom {XMLNode} xml dom to serialize
- * 
- * Returns:
- * {?}
- */
-OpenLayers.Ajax.serializeXMLToString = function(xmldom) {
-    var serializer = new XMLSerializer();
-    var data = serializer.serializeToString(xmldom);
-    return data;
-};
-/* ======================================================================
-    OpenLayers/Console.js
-   ====================================================================== */
+      if ( !this.options.compact && (n == (this.options.numSlices-1)) ) {
+         inStyle.height = "2px";
+      }
+      this._setMargin(slice, n, position);
+      this._setBorder(slice, n, position);
+      return slice;
+   },
 
-/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
- * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
- * full text of the license. */
+   _setOptions: function(options) {
+      this.options = {
+         corners : "all",
+         color   : "fromElement",
+         bgColor : "fromParent",
+         blend   : true,
+         border  : false,
+         compact : false
+      };
+      OpenLayers.Util.extend(this.options, options || {});
 
-/**
- * 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() {},
+      this.options.numSlices = this.options.compact ? 2 : 4;
+      if ( this._isTransparent() ) {
+         this.options.blend = false;
+      }
+   },
 
-    /**
-     * 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() {},
+   _whichSideTop: function() {
+      if ( this._hasString(this.options.corners, "all", "top") ) {
+         return "";
+      }
+      if ( this.options.corners.indexOf("tl") >= 0 && this.options.corners.indexOf("tr") >= 0 ) {
+         return "";
+      }
+      if (this.options.corners.indexOf("tl") >= 0) {
+         return "left";
+      } else if (this.options.corners.indexOf("tr") >= 0) {
+          return "right";
+      }
+      return "";
+   },
 
-    /**
-     * 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() {},
+   _whichSideBottom: function() {
+      if ( this._hasString(this.options.corners, "all", "bottom") ) {
+         return "";
+      }
+      if ( this.options.corners.indexOf("bl")>=0 && this.options.corners.indexOf("br")>=0 ) {
+         return "";
+      }
 
-    /**
-     * 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() {},
+      if(this.options.corners.indexOf("bl") >=0) {
+         return "left";
+      } else if(this.options.corners.indexOf("br")>=0) {
+         return "right";
+      }
+      return "";
+   },
 
-    /**
-     * 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() {},
+   _borderColor : function(color,bgColor) {
+      if ( color == "transparent" ) {
+         return bgColor;
+      } else if ( this.options.border ) {
+         return this.options.border;
+      } else if ( this.options.blend ) {
+         return this._blend( bgColor, color );
+      } else {
+         return "";
+      }
+   },
 
-    /**
-     * 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() {},
+   _setMargin: function(el, n, corners) {
+      var marginSize = this._marginSize(n);
+      var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
 
-    /**
-     * 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() {},
+      if ( whichSide == "left" ) {
+         el.style.marginLeft = marginSize + "px"; el.style.marginRight = "0px";
+      }
+      else if ( whichSide == "right" ) {
+         el.style.marginRight = marginSize + "px"; el.style.marginLeft  = "0px";
+      }
+      else {
+         el.style.marginLeft = marginSize + "px"; el.style.marginRight = marginSize + "px";
+      }
+   },
 
-    /**
-     * 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() {},
+   _setBorder: function(el,n,corners) {
+      var borderSize = this._borderSize(n);
+      var whichSide = corners == "top" ? this._whichSideTop() : this._whichSideBottom();
+      if ( whichSide == "left" ) {
+         el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = "0px";
+      }
+      else if ( whichSide == "right" ) {
+         el.style.borderRightWidth = borderSize + "px"; el.style.borderLeftWidth  = "0px";
+      }
+      else {
+         el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+      }
+      if (this.options.border != false) {
+        el.style.borderLeftWidth = borderSize + "px"; el.style.borderRightWidth = borderSize + "px";
+      }
+   },
 
-    /**
-     * 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() {},
+   _marginSize: function(n) {
+      if ( this._isTransparent() ) {
+         return 0;
+      }
+      var marginSizes          = [ 5, 3, 2, 1 ];
+      var blendedMarginSizes   = [ 3, 2, 1, 0 ];
+      var compactMarginSizes   = [ 2, 1 ];
+      var smBlendedMarginSizes = [ 1, 0 ];
 
-    /**
-     * 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() {},
+      if ( this.options.compact && this.options.blend ) {
+         return smBlendedMarginSizes[n];
+      } else if ( this.options.compact ) {
+         return compactMarginSizes[n];
+      } else if ( this.options.blend ) {
+         return blendedMarginSizes[n];
+      } else {
+         return marginSizes[n];
+      }
+   },
 
-    /**
-     * APIFunction: timeEnd
-     * Stops a timer created by a call to OpenLayers.Console.time(name) and
-     * writes the time elapsed.
-     *
-     * Parameters:
-     * name - {String}
-     */
-    timeEnd: function() {},
+   _borderSize: function(n) {
+      var transparentBorderSizes = [ 5, 3, 2, 1 ];
+      var blendedBorderSizes     = [ 2, 1, 1, 1 ];
+      var compactBorderSizes     = [ 1, 0 ];
+      var actualBorderSizes      = [ 0, 2, 0, 0 ];
 
-    /**
-     * 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() {},
+      if ( this.options.compact && (this.options.blend || this._isTransparent()) ) {
+         return 1;
+      } else if ( this.options.compact ) {
+         return compactBorderSizes[n];
+      } else if ( this.options.blend ) {
+         return blendedBorderSizes[n];
+      } else if ( this.options.border ) {
+         return actualBorderSizes[n];
+      } else if ( this._isTransparent() ) {
+         return transparentBorderSizes[n];
+      }
+      return 0;
+   },
 
-    /**
-     * 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"
+   _hasString: function(str) { for(var i=1 ; i<arguments.length ; i++) if (str.indexOf(arguments[i]) >= 0) { return true; } return false; },
+   _blend: function(c1, c2) { var cc1 = OpenLayers.Rico.Color.createFromHex(c1); cc1.blend(OpenLayers.Rico.Color.createFromHex(c2)); return cc1; },
+   _background: function(el) { try { return OpenLayers.Rico.Color.createColorFromBackground(el).asHex(); } catch(err) { return "#ffffff"; } },
+   _isTransparent: function() { return this.options.color == "transparent"; },
+   _isTopRounded: function() { return this._hasString(this.options.corners, "all", "top", "tl", "tr"); },
+   _isBottomRounded: function() { return this._hasString(this.options.corners, "all", "bottom", "bl", "br"); },
+   _hasSingleTextChild: function(el) { return el.childNodes.length == 1 && el.childNodes[0].nodeType == 3; }
 };
-
-/**
- * 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.
-     */
-    if(window.console) {
-        var scripts = document.getElementsByTagName("script");
-        for(var i=0; i<scripts.length; ++i) {
-            if(scripts[i].src.indexOf("firebug.js") != -1) {
-                OpenLayers.Util.extend(OpenLayers.Console, console);
-                break;
-            }
-        }
-    }
-})();
 /* ======================================================================
-    OpenLayers/BaseTypes/Size.js
-   ====================================================================== */
-
-/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
- * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
- * full text of the license. */
-
-/**
- * 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. 
-     * (ex. <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>}
-     *
-     * 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/BaseTypes/Bounds.js
    ====================================================================== */
 
@@ -3574,6 +2982,48 @@
     },
 
     /**
+     * Method: scale
+     * Scales the bounds around a pixel or lonlat. Note that the new 
+     *     bounds may return non-integer properties, even if a pixel
+     *     is passed. 
+     * 
+     * Parameters:
+     * ratio - {Float} 
+     * origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
+     *          Default is center.
+     *
+     * Returns:
+     * {<OpenLayers.Bound>} A new bounds that is scaled by ratio
+     *                      from origin.
+     */
+
+    scale: function(ratio, origin){
+        if(origin == null){
+            origin = this.getCenterLonLat();
+        }
+
+        var bounds = [];
+        
+        var origx,origy;
+
+        // get origin coordinates
+        if(origin.CLASS_NAME == "OpenLayers.LonLat"){
+            origx = origin.lon;
+            origy = origin.lat;
+        } else {
+            origx = origin.x;
+            origy = origin.y;
+        }
+
+        var left = (this.left - origx) * ratio + origx;
+        var bottom = (this.bottom - origy) * ratio + origy;
+        var right = (this.right - origx) * ratio + origx;
+        var top = (this.top - origy) * ratio + origy;
+
+        return new OpenLayers.Bounds(left, bottom, right, top);
+    },
+
+    /**
      * APIMethod: add
      * 
      * Parameters:
@@ -3987,7 +3437,7 @@
      * element - {DOMElement} Actually user can pass any number of elements
      */
     toggle: function() {
-        for (var i = 0; i < arguments.length; i++) {
+        for (var i=0, len=arguments.length; i<len; i++) {
             var element = OpenLayers.Util.getElement(arguments[i]);
             var display = OpenLayers.Element.visible(element) ? 'hide' 
                                                               : 'show';
@@ -4004,7 +3454,7 @@
      * element - {DOMElement} Actually user can pass any number of elements
      */
     hide: function() {
-        for (var i = 0; i < arguments.length; i++) {
+        for (var i=0, len=arguments.length; i<len; i++) {
             var element = OpenLayers.Util.getElement(arguments[i]);
             element.style.display = 'none';
         }
@@ -4018,7 +3468,7 @@
      * element - {DOMElement} Actually user can pass any number of elements
      */
     show: function() {
-        for (var i = 0; i < arguments.length; i++) {
+        for (var i=0, len=arguments.length; i<len; i++) {
             var element = OpenLayers.Util.getElement(arguments[i]);
             element.style.display = '';
         }
@@ -4083,6 +3533,86 @@
     },
 
     /**
+     * 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:
@@ -4094,25 +3624,29 @@
      */
     getStyle: function(element, style) {
         element = OpenLayers.Util.getElement(element);
-        var 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 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';
+            }
         }
     
-        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;
     }
 
@@ -4237,7 +3771,8 @@
 
     /**
      * APIMethod: transform
-     * Transform the LonLat object from source to dest. 
+     * 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. 
@@ -4430,6 +3965,343 @@
     CLASS_NAME: "OpenLayers.Pixel"
 });
 /* ======================================================================
+    OpenLayers/BaseTypes/Size.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * 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. 
+     * (ex. <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>}
+     *
+     * 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-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * 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:
+     * object - {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.
+     */
+    if(window.console) {
+        var scripts = document.getElementsByTagName("script");
+        for(var i=0, len=scripts.length; i<len; ++i) {
+            if(scripts[i].src.indexOf("firebug.js") != -1) {
+                OpenLayers.Util.extend(OpenLayers.Console, console);
+                break;
+            }
+        }
+    }
+})();
+/* ======================================================================
     OpenLayers/Control.js
    ====================================================================== */
 
@@ -4478,7 +4350,7 @@
  * >     },
  * >
  * >     notice: function (bounds) {
- * >         alert(bounds);
+ * >         OpenLayers.Console.userError(bounds);
  * >     }
  * > }); 
  * > map.addControl(control);
@@ -4611,7 +4483,9 @@
         if(this.eventListeners instanceof Object) {
             this.events.on(this.eventListeners);
         }
-        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+        if (this.id == null) {
+            this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+        }
     },
 
     /**
@@ -5100,6 +4974,1841 @@
  */
 OpenLayers.i18n = OpenLayers.Lang.translate;
 /* ======================================================================
+    OpenLayers/Popup.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * Class: OpenLayers.Popup
+ * A popup is a small div that can opened and closed on the map.
+ * Typically opened in response to clicking on a marker.  
+ * See <OpenLayers.Marker>.  Popup's don't require their own
+ * layer and are added the the map using the <OpenLayers.Map.addPopup>
+ * method.
+ *
+ * Example:
+ * (code)
+ * popup = new OpenLayers.Popup("chicken", 
+ *                    new OpenLayers.LonLat(5,40),
+ *                    new OpenLayers.Size(200,200),
+ *                    "example popup",
+ *                    true);
+ *       
+ * map.addPopup(popup);
+ * (end)
+ */
+OpenLayers.Popup = OpenLayers.Class({
+
+    /** 
+     * Property: events  
+     * {<OpenLayers.Events>} custom event manager 
+     */
+    events: null,
+    
+    /** Property: id
+     * {String} the unique identifier assigned to this popup.
+     */
+    id: "",
+
+    /** 
+     * Property: lonlat 
+     * {<OpenLayers.LonLat>} the position of this popup on the map
+     */
+    lonlat: null,
+
+    /** 
+     * Property: div 
+     * {DOMElement} the div that contains this popup.
+     */
+    div: null,
+
+    /** 
+     * Property: contentSize 
+     * {<OpenLayers.Size>} the width and height of the content.
+     */
+    contentSize: null,    
+
+    /** 
+     * Property: size 
+     * {<OpenLayers.Size>} the width and height of the popup.
+     */
+    size: null,    
+
+    /** 
+     * Property: contentHTML 
+     * {String} An HTML string for this popup to display.
+     */
+    contentHTML: null,
+    
+    /** 
+     * Property: backgroundColor 
+     * {String} the background color used by the popup.
+     */
+    backgroundColor: "",
+    
+    /** 
+     * Property: opacity 
+     * {float} the opacity of this popup (between 0.0 and 1.0)
+     */
+    opacity: "",
+
+    /** 
+     * Property: border 
+     * {String} the border size of the popup.  (eg 2px)
+     */
+    border: "",
+    
+    /** 
+     * Property: contentDiv 
+     * {DOMElement} a reference to the element that holds the content of
+     *              the div.
+     */
+    contentDiv: null,
+    
+    /** 
+     * Property: groupDiv 
+     * {DOMElement} First and only child of 'div'. The group Div contains the
+     *     'contentDiv' and the 'closeDiv'.
+     */
+    groupDiv: null,
+
+    /** 
+     * Property: closeDiv
+     * {DOMElement} the optional closer image
+     */
+    closeDiv: null,
+
+    /** 
+     * APIProperty: autoSize
+     * {Boolean} Resize the popup to auto-fit the contents.
+     *     Default is false.
+     */
+    autoSize: false,
+
+    /**
+     * APIProperty: minSize
+     * {<OpenLayers.Size>} Minimum size allowed for the popup's contents.
+     */
+    minSize: null,
+
+    /**
+     * APIProperty: maxSize
+     * {<OpenLayers.Size>} Maximum size allowed for the popup's contents.
+     */
+    maxSize: null,
+
+    /** 
+     * Property: displayClass
+     * {String} The CSS class of the popup.
+     */
+    displayClass: "olPopup",
+
+    /** 
+     * Property: contentDisplayClass
+     * {String} The CSS class of the popup content div.
+     */
+    contentDisplayClass: "olPopupContent",
+
+    /** 
+     * Property: padding 
+     * {int or <OpenLayers.Bounds>} An extra opportunity to specify internal 
+     *     padding of the content div inside the popup. This was originally
+     *     confused with the css padding as specified in style.css's 
+     *     'olPopupContent' class. We would like to get rid of this altogether,
+     *     except that it does come in handy for the framed and anchoredbubble
+     *     popups, who need to maintain yet another barrier between their 
+     *     content and the outer border of the popup itself. 
+     * 
+     *     Note that in order to not break API, we must continue to support 
+     *     this property being set as an integer. Really, though, we'd like to 
+     *     have this specified as a Bounds object so that user can specify
+     *     distinct left, top, right, bottom paddings. With the 3.0 release
+     *     we can make this only a bounds.
+     */
+    padding: 0,
+
+    /** 
+     * Method: fixPadding
+     * To be removed in 3.0, this function merely helps us to deal with the 
+     *     case where the user may have set an integer value for padding, 
+     *     instead of an <OpenLayers.Bounds> object.
+     */
+    fixPadding: function() {
+        if (typeof this.padding == "number") {
+            this.padding = new OpenLayers.Bounds(
+                this.padding, this.padding, this.padding, this.padding
+            );
+        }
+    },
+
+    /**
+     * APIProperty: panMapIfOutOfView
+     * {Boolean} When drawn, pan map such that the entire popup is visible in
+     *     the current viewport (if necessary).
+     *     Default is false.
+     */
+    panMapIfOutOfView: false,
+    
+    /** 
+     * Property: map 
+     * {<OpenLayers.Map>} this gets set in Map.js when the popup is added to the map
+     */
+    map: null,
+
+    /** 
+    * Constructor: OpenLayers.Popup
+    * Create a popup.
+    * 
+    * Parameters: 
+    * id - {String} a unqiue identifier for this popup.  If null is passed
+    *               an identifier will be automatically generated. 
+    * lonlat - {<OpenLayers.LonLat>}  The position on the map the popup will
+    *                                 be shown.
+    * contentSize - {<OpenLayers.Size>} The size of the content.
+    * contentHTML - {String}          An HTML string to display inside the   
+    *                                 popup.
+    * closeBox - {Boolean}            Whether to display a close box inside
+    *                                 the popup.
+    * closeBoxCallback - {Function}   Function to be called on closeBox click.
+    */
+    initialize:function(id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback) {
+        if (id == null) {
+            id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+        }
+
+        this.id = id;
+        this.lonlat = lonlat;
+
+        this.contentSize = (contentSize != null) ? contentSize 
+                                  : new OpenLayers.Size(
+                                                   OpenLayers.Popup.WIDTH,
+                                                   OpenLayers.Popup.HEIGHT);
+        if (contentHTML != null) { 
+             this.contentHTML = contentHTML;
+        }
+        this.backgroundColor = OpenLayers.Popup.COLOR;
+        this.opacity = OpenLayers.Popup.OPACITY;
+        this.border = OpenLayers.Popup.BORDER;
+
+        this.div = OpenLayers.Util.createDiv(this.id, null, null, 
+                                             null, null, null, "hidden");
+        this.div.className = this.displayClass;
+        
+        var groupDivId = this.id + "_GroupDiv";
+        this.groupDiv = OpenLayers.Util.createDiv(groupDivId, null, null, 
+                                                    null, "relative", null,
+                                                    "hidden");
+
+        var id = this.div.id + "_contentDiv";
+        this.contentDiv = OpenLayers.Util.createDiv(id, null, this.contentSize.clone(), 
+                                                    null, "relative");
+        this.contentDiv.className = this.contentDisplayClass;
+        this.groupDiv.appendChild(this.contentDiv);
+        this.div.appendChild(this.groupDiv);
+
+        if (closeBox) {
+            this.addCloseBox(closeBoxCallback);
+        } 
+
+        this.registerEvents();
+    },
+
+    /** 
+     * Method: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+
+        this.id = null;
+        this.lonlat = null;
+        this.size = null;
+        this.contentHTML = null;
+        
+        this.backgroundColor = null;
+        this.opacity = null;
+        this.border = null;
+        
+        this.events.destroy();
+        this.events = null;
+        
+        if (this.closeDiv) {
+            OpenLayers.Event.stopObservingElement(this.closeDiv); 
+            this.groupDiv.removeChild(this.closeDiv);
+        }
+        this.closeDiv = null;
+        
+        this.div.removeChild(this.groupDiv);
+        this.groupDiv = null;
+
+        if (this.map != null) {
+            this.map.removePopup(this);
+        }
+        this.map = null;
+        this.div = null;
+        
+        this.autoSize = null;
+        this.minSize = null;
+        this.maxSize = null;
+        this.padding = null;
+        this.panMapIfOutOfView = null;
+    },
+
+    /** 
+    * Method: draw
+    * Constructs the elements that make up the popup.
+    *
+    * Parameters:
+    * px - {<OpenLayers.Pixel>} the position the popup in pixels.
+    * 
+    * Returns:
+    * {DOMElement} Reference to a div that contains the drawn popup
+    */
+    draw: function(px) {
+        if (px == null) {
+            if ((this.lonlat != null) && (this.map != null)) {
+                px = this.map.getLayerPxFromLonLat(this.lonlat);
+            }
+        }
+        
+        //listen to movestart, moveend to disable overflow (FF bug)
+        if (OpenLayers.Util.getBrowserName() == 'firefox') {
+            this.map.events.register("movestart", this, function() {
+                var style = document.defaultView.getComputedStyle(
+                    this.contentDiv, null
+                );
+                var currentOverflow = style.getPropertyValue("overflow");
+                if (currentOverflow != "hidden") {
+                    this.contentDiv._oldOverflow = currentOverflow;
+                    this.contentDiv.style.overflow = "hidden";
+                }
+            });
+            this.map.events.register("moveend", this, function() {
+                var oldOverflow = this.contentDiv._oldOverflow;
+                if (oldOverflow) {
+                    this.contentDiv.style.overflow = oldOverflow;
+                    this.contentDiv._oldOverflow = null;
+                }
+            });
+        }
+
+        this.moveTo(px);
+        if (!this.autoSize && !this.size) {
+            this.setSize(this.contentSize);
+        }
+        this.setBackgroundColor();
+        this.setOpacity();
+        this.setBorder();
+        this.setContentHTML();
+        
+        if (this.panMapIfOutOfView) {
+            this.panIntoView();
+        }    
+
+        return this.div;
+    },
+
+    /** 
+     * Method: updatePosition
+     * if the popup has a lonlat and its map members set, 
+     * then have it move itself to its proper position
+     */
+    updatePosition: function() {
+        if ((this.lonlat) && (this.map)) {
+            var px = this.map.getLayerPxFromLonLat(this.lonlat);
+            if (px) {
+                this.moveTo(px);           
+            }    
+        }
+    },
+
+    /**
+     * Method: moveTo
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>} the top and left position of the popup div. 
+     */
+    moveTo: function(px) {
+        if ((px != null) && (this.div != null)) {
+            this.div.style.left = px.x + "px";
+            this.div.style.top = px.y + "px";
+        }
+    },
+
+    /**
+     * Method: visible
+     *
+     * Returns:      
+     * {Boolean} Boolean indicating whether or not the popup is visible
+     */
+    visible: function() {
+        return OpenLayers.Element.visible(this.div);
+    },
+
+    /**
+     * Method: toggle
+     * Toggles visibility of the popup.
+     */
+    toggle: function() {
+        if (this.visible()) {
+            this.hide();
+        } else {
+            this.show();
+        }
+    },
+
+    /**
+     * Method: show
+     * Makes the popup visible.
+     */
+    show: function() {
+        OpenLayers.Element.show(this.div);
+
+        if (this.panMapIfOutOfView) {
+            this.panIntoView();
+        }    
+    },
+
+    /**
+     * Method: hide
+     * Makes the popup invisible.
+     */
+    hide: function() {
+        OpenLayers.Element.hide(this.div);
+    },
+
+    /**
+     * Method: setSize
+     * Used to adjust the size of the popup. 
+     *
+     * Parameters:
+     * contentSize - {<OpenLayers.Size>} the new size for the popup's 
+     *     contents div (in pixels).
+     */
+    setSize:function(contentSize) { 
+        this.size = contentSize.clone(); 
+        
+        // if our contentDiv has a css 'padding' set on it by a stylesheet, we 
+        //  must add that to the desired "size". 
+        var contentDivPadding = this.getContentDivPadding();
+        var wPadding = contentDivPadding.left + contentDivPadding.right;
+        var hPadding = contentDivPadding.top + contentDivPadding.bottom;
+
+        // take into account the popup's 'padding' property
+        this.fixPadding();
+        wPadding += this.padding.left + this.padding.right;
+        hPadding += this.padding.top + this.padding.bottom;
+
+        // make extra space for the close div
+        if (this.closeDiv) {
+            var closeDivWidth = parseInt(this.closeDiv.style.width);
+            wPadding += closeDivWidth + contentDivPadding.right;
+        }
+
+        //increase size of the main popup div to take into account the 
+        // users's desired padding and close div.        
+        this.size.w += wPadding;
+        this.size.h += hPadding;
+
+        //now if our browser is IE, we need to actually make the contents 
+        // div itself bigger to take its own padding into effect. this makes 
+        // me want to shoot someone, but so it goes.
+        if (OpenLayers.Util.getBrowserName() == "msie") {
+            this.contentSize.w += 
+                contentDivPadding.left + contentDivPadding.right;
+            this.contentSize.h += 
+                contentDivPadding.bottom + contentDivPadding.top;
+        }
+
+        if (this.div != null) {
+            this.div.style.width = this.size.w + "px";
+            this.div.style.height = this.size.h + "px";
+        }
+        if (this.contentDiv != null){
+            this.contentDiv.style.width = contentSize.w + "px";
+            this.contentDiv.style.height = contentSize.h + "px";
+        }
+    },  
+
+    /**
+     * APIMethod: updateSize
+     * Auto size the popup so that it precisely fits its contents (as 
+     *     determined by this.contentDiv.innerHTML). Popup size will, of
+     *     course, be limited by the available space on the current map
+     */
+    updateSize: function() {
+        
+        // determine actual render dimensions of the contents by putting its
+        // contents into a fake contentDiv (for the CSS) and then measuring it
+        var preparedHTML = "<div class='" + this.contentDisplayClass+ "'>" + 
+            this.contentDiv.innerHTML + 
+            "<div>";
+        var realSize = OpenLayers.Util.getRenderedDimensions(
+            preparedHTML, null, { displayClass: this.displayClass }
+        );
+
+        // is the "real" size of the div is safe to display in our map?
+        var safeSize = this.getSafeContentSize(realSize);
+
+        var newSize = null;
+        if (safeSize.equals(realSize)) {
+            //real size of content is small enough to fit on the map, 
+            // so we use real size.
+            newSize = realSize;
+
+        } else {
+
+            //make a new OL.Size object with the clipped dimensions 
+            // set or null if not clipped.
+            var fixedSize = new OpenLayers.Size();
+            fixedSize.w = (safeSize.w < realSize.w) ? safeSize.w : null;
+            fixedSize.h = (safeSize.h < realSize.h) ? safeSize.h : null;
+        
+            if (fixedSize.w && fixedSize.h) {
+                //content is too big in both directions, so we will use 
+                // max popup size (safeSize), knowing well that it will 
+                // overflow both ways.                
+                newSize = safeSize;
+            } else {
+                //content is clipped in only one direction, so we need to 
+                // run getRenderedDimensions() again with a fixed dimension
+                var clippedSize = OpenLayers.Util.getRenderedDimensions(
+                    preparedHTML, fixedSize, 
+                    { displayClass: this.contentDisplayClass }
+                );
+                
+                //if the clipped size is still the same as the safeSize, 
+                // that means that our content must be fixed in the 
+                // offending direction. If overflow is 'auto', this means 
+                // we are going to have a scrollbar for sure, so we must 
+                // adjust for that.
+                //
+                var currentOverflow = OpenLayers.Element.getStyle(
+                    this.contentDiv, "overflow"
+                );
+                if ( (currentOverflow != "hidden") && 
+                     (clippedSize.equals(safeSize)) ) {
+                    var scrollBar = OpenLayers.Util.getScrollbarWidth();
+                    if (fixedSize.w) {
+                        clippedSize.h += scrollBar;
+                    } else {
+                        clippedSize.w += scrollBar;
+                    }
+                }
+                
+                newSize = this.getSafeContentSize(clippedSize);
+            }
+        }                        
+        this.setSize(newSize);     
+    },    
+
+    /**
+     * Method: setBackgroundColor
+     * Sets the background color of the popup.
+     *
+     * Parameters:
+     * color - {String} the background color.  eg "#FFBBBB"
+     */
+    setBackgroundColor:function(color) { 
+        if (color != undefined) {
+            this.backgroundColor = color; 
+        }
+        
+        if (this.div != null) {
+            this.div.style.backgroundColor = this.backgroundColor;
+        }
+    },  
+    
+    /**
+     * Method: setOpacity
+     * Sets the opacity of the popup.
+     * 
+     * Parameters:
+     * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).   
+     */
+    setOpacity:function(opacity) { 
+        if (opacity != undefined) {
+            this.opacity = opacity; 
+        }
+        
+        if (this.div != null) {
+            // for Mozilla and Safari
+            this.div.style.opacity = this.opacity;
+
+            // for IE
+            this.div.style.filter = 'alpha(opacity=' + this.opacity*100 + ')';
+        }
+    },  
+    
+    /**
+     * Method: setBorder
+     * Sets the border style of the popup.
+     *
+     * Parameters:
+     * border - {String} The border style value. eg 2px 
+     */
+    setBorder:function(border) { 
+        if (border != undefined) {
+            this.border = border;
+        }
+        
+        if (this.div != null) {
+            this.div.style.border = this.border;
+        }
+    },      
+    
+    /**
+     * Method: setContentHTML
+     * Allows the user to set the HTML content of the popup.
+     *
+     * Parameters:
+     * contentHTML - {String} HTML for the div.
+     */
+    setContentHTML:function(contentHTML) {
+
+        if (contentHTML != null) {
+            this.contentHTML = contentHTML;
+        }
+       
+        if ((this.contentDiv != null) && 
+            (this.contentHTML != null) &&
+            (this.contentHTML != this.contentDiv.innerHTML)) {
+       
+            this.contentDiv.innerHTML = this.contentHTML;
+       
+            if (this.autoSize) {
+                
+                //if popup has images, listen for when they finish
+                // loading and resize accordingly
+                this.registerImageListeners();
+
+                //auto size the popup to its current contents
+                this.updateSize();
+            }
+        }    
+
+    },
+    
+    /**
+     * Method: registerImageListeners
+     * Called when an image contained by the popup loaded. this function
+     *     updates the popup size, then unregisters the image load listener.
+     */   
+    registerImageListeners: function() { 
+
+        // As the images load, this function will call updateSize() to 
+        // resize the popup to fit the content div (which presumably is now
+        // bigger than when the image was not loaded).
+        // 
+        // If the 'panMapIfOutOfView' property is set, we will pan the newly
+        // resized popup back into view.
+        // 
+        // Note that this function, when called, will have 'popup' and 
+        // 'img' properties in the context.
+        //
+        var onImgLoad = function() {
+            
+            this.popup.updateSize();
+     
+            if ( this.popup.visible() && this.popup.panMapIfOutOfView ) {
+                this.popup.panIntoView();
+            }
+
+            OpenLayers.Event.stopObserving(
+                this.img, "load", this.img._onImageLoad
+            );
+    
+        };
+
+        //cycle through the images and if their size is 0x0, that means that 
+        // they haven't been loaded yet, so we attach the listener, which 
+        // will fire when the images finish loading and will resize the 
+        // popup accordingly to its new size.
+        var images = this.contentDiv.getElementsByTagName("img");
+        for (var i = 0, len = images.length; i < len; i++) {
+            var img = images[i];
+            if (img.width == 0 || img.height == 0) {
+
+                var context = {
+                    'popup': this,
+                    'img': img
+                };
+
+                //expando this function to the image itself before registering
+                // it. This way we can easily and properly unregister it.
+                img._onImgLoad = OpenLayers.Function.bind(onImgLoad, context);
+
+                OpenLayers.Event.observe(img, 'load', img._onImgLoad);
+            }    
+        } 
+    },
+
+    /**
+     * APIMethod: getSafeContentSize
+     * 
+     * Parameters:
+     * size - {<OpenLayers.Size>} Desired size to make the popup.
+     * 
+     * Returns:
+     * {<OpenLayers.Size>} A size to make the popup which is neither smaller
+     *     than the specified minimum size, nor bigger than the maximum 
+     *     size (which is calculated relative to the size of the viewport).
+     */
+    getSafeContentSize: function(size) {
+
+        var safeContentSize = size.clone();
+
+        // if our contentDiv has a css 'padding' set on it by a stylesheet, we 
+        //  must add that to the desired "size". 
+        var contentDivPadding = this.getContentDivPadding();
+        var wPadding = contentDivPadding.left + contentDivPadding.right;
+        var hPadding = contentDivPadding.top + contentDivPadding.bottom;
+
+        // take into account the popup's 'padding' property
+        this.fixPadding();
+        wPadding += this.padding.left + this.padding.right;
+        hPadding += this.padding.top + this.padding.bottom;
+
+        if (this.closeDiv) {
+            var closeDivWidth = parseInt(this.closeDiv.style.width);
+            wPadding += closeDivWidth + contentDivPadding.right;
+        }
+
+        // prevent the popup from being smaller than a specified minimal size
+        if (this.minSize) {
+            safeContentSize.w = Math.max(safeContentSize.w, 
+                (this.minSize.w - wPadding));
+            safeContentSize.h = Math.max(safeContentSize.h, 
+                (this.minSize.h - hPadding));
+        }
+
+        // prevent the popup from being bigger than a specified maximum size
+        if (this.maxSize) {
+            safeContentSize.w = Math.min(safeContentSize.w, 
+                (this.maxSize.w - wPadding));
+            safeContentSize.h = Math.min(safeContentSize.h, 
+                (this.maxSize.h - hPadding));
+        }
+        
+        //make sure the desired size to set doesn't result in a popup that 
+        // is bigger than the map's viewport.
+        //
+        if (this.map && this.map.size) {
+
+            // Note that there *was* a reference to a
+            // 'OpenLayers.Popup.SCROLL_BAR_WIDTH' constant here, with special
+            // tolerance for it and everything... but it was never defined in
+            // the first place, so I don't know what to think.
+          
+            var maxY = this.map.size.h - 
+                this.map.paddingForPopups.top - 
+                this.map.paddingForPopups.bottom - 
+                hPadding;
+    
+            var maxX = this.map.size.w - 
+                this.map.paddingForPopups.left - 
+                this.map.paddingForPopups.right - 
+                wPadding;
+    
+            safeContentSize.w = Math.min(safeContentSize.w, maxX);
+            safeContentSize.h = Math.min(safeContentSize.h, maxY);
+        }
+        
+        return safeContentSize;
+    },
+    
+    /**
+     * Method: getContentDivPadding
+     * Glorious, oh glorious hack in order to determine the css 'padding' of 
+     *     the contentDiv. IE/Opera return null here unless we actually add the 
+     *     popup's main 'div' element (which contains contentDiv) to the DOM. 
+     *     So we make it invisible and then add it to the document temporarily. 
+     *
+     *     Once we've taken the padding readings we need, we then remove it 
+     *     from the DOM (it will actually get added to the DOM in 
+     *     Map.js's addPopup)
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>}
+     */
+    getContentDivPadding: function() {
+
+        //use cached value if we have it
+        var contentDivPadding = this._contentDivPadding;
+        if (!contentDivPadding) {
+            //make the div invisible and add it to the page        
+            this.div.style.display = "none";
+            document.body.appendChild(this.div);
+    
+            //read the padding settings from css, put them in an OL.Bounds        
+            contentDivPadding = new OpenLayers.Bounds(
+                OpenLayers.Element.getStyle(this.contentDiv, "padding-left"),
+                OpenLayers.Element.getStyle(this.contentDiv, "padding-bottom"),
+                OpenLayers.Element.getStyle(this.contentDiv, "padding-right"),
+                OpenLayers.Element.getStyle(this.contentDiv, "padding-top")
+            );
+    
+            //cache the value
+            this._contentDivPadding = contentDivPadding;
+    
+            //remove the div from the page and make it visible again
+            document.body.removeChild(this.div);
+            this.div.style.display = "";
+        }
+        return contentDivPadding;
+    },
+
+    /**
+     * Method: addCloseBox
+     * 
+     * Parameters:
+     * callback - {Function} The callback to be called when the close button
+     *     is clicked.
+     */
+    addCloseBox: function(callback) {
+
+        this.closeDiv = OpenLayers.Util.createDiv(
+            this.id + "_close", null, new OpenLayers.Size(17, 17)
+        );
+        this.closeDiv.className = "olPopupCloseBox"; 
+        
+        // use the content div's css padding to determine if we should
+        //  padd the close div
+        var contentDivPadding = this.getContentDivPadding();
+         
+        this.closeDiv.style.right = contentDivPadding.right + "px";
+        this.closeDiv.style.top = contentDivPadding.top + "px";
+        this.groupDiv.appendChild(this.closeDiv);
+
+        var closePopup = callback || function(e) {
+            this.hide();
+            OpenLayers.Event.stop(e);
+        };
+        OpenLayers.Event.observe(this.closeDiv, "click", 
+                OpenLayers.Function.bindAsEventListener(closePopup, this));
+    },
+
+    /**
+     * Method: panIntoView
+     * Pans the map such that the popup is totaly viewable (if necessary)
+     */
+    panIntoView: function() {
+        
+        var mapSize = this.map.getSize();
+    
+        //start with the top left corner of the popup, in px, 
+        // relative to the viewport
+        var origTL = this.map.getViewPortPxFromLayerPx( new OpenLayers.Pixel(
+            parseInt(this.div.style.left),
+            parseInt(this.div.style.top)
+        ));
+        var newTL = origTL.clone();
+    
+        //new left (compare to margins, using this.size to calculate right)
+        if (origTL.x < this.map.paddingForPopups.left) {
+            newTL.x = this.map.paddingForPopups.left;
+        } else 
+        if ( (origTL.x + this.size.w) > (mapSize.w - this.map.paddingForPopups.right)) {
+            newTL.x = mapSize.w - this.map.paddingForPopups.right - this.size.w;
+        }
+        
+        //new top (compare to margins, using this.size to calculate bottom)
+        if (origTL.y < this.map.paddingForPopups.top) {
+            newTL.y = this.map.paddingForPopups.top;
+        } else 
+        if ( (origTL.y + this.size.h) > (mapSize.h - this.map.paddingForPopups.bottom)) {
+            newTL.y = mapSize.h - this.map.paddingForPopups.bottom - this.size.h;
+        }
+        
+        var dx = origTL.x - newTL.x;
+        var dy = origTL.y - newTL.y;
+        
+        this.map.pan(dx, dy);
+    },
+
+    /** 
+     * Method: registerEvents
+     * Registers events on the popup.
+     *
+     * Do this in a separate function so that subclasses can 
+     *   choose to override it if they wish to deal differently
+     *   with mouse events
+     * 
+     *   Note in the following handler functions that some special
+     *    care is needed to deal correctly with mousing and popups. 
+     *   
+     *   Because the user might select the zoom-rectangle option and
+     *    then drag it over a popup, we need a safe way to allow the
+     *    mousemove and mouseup events to pass through the popup when
+     *    they are initiated from outside.
+     * 
+     *   Otherwise, we want to essentially kill the event propagation
+     *    for all other events, though we have to do so carefully, 
+     *    without disabling basic html functionality, like clicking on 
+     *    hyperlinks or drag-selecting text.
+     */
+     registerEvents:function() {
+        this.events = new OpenLayers.Events(this, this.div, null, true);
+
+        this.events.on({
+            "mousedown": this.onmousedown,
+            "mousemove": this.onmousemove,
+            "mouseup": this.onmouseup,
+            "click": this.onclick,
+            "mouseout": this.onmouseout,
+            "dblclick": this.ondblclick,
+            scope: this
+        });
+        
+     },
+
+    /** 
+     * Method: onmousedown 
+     * When mouse goes down within the popup, make a note of
+     *   it locally, and then do not propagate the mousedown 
+     *   (but do so safely so that user can select text inside)
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    onmousedown: function (evt) {
+        this.mousedown = true;
+        OpenLayers.Event.stop(evt, true);
+    },
+
+    /** 
+     * Method: onmousemove
+     * If the drag was started within the popup, then 
+     *   do not propagate the mousemove (but do so safely
+     *   so that user can select text inside)
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    onmousemove: function (evt) {
+        if (this.mousedown) {
+            OpenLayers.Event.stop(evt, true);
+        }
+    },
+
+    /** 
+     * Method: onmouseup
+     * When mouse comes up within the popup, after going down 
+     *   in it, reset the flag, and then (once again) do not 
+     *   propagate the event, but do so safely so that user can 
+     *   select text inside
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    onmouseup: function (evt) {
+        if (this.mousedown) {
+            this.mousedown = false;
+            OpenLayers.Event.stop(evt, true);
+        }
+    },
+
+    /**
+     * Method: onclick
+     * Ignore clicks, but allowing default browser handling
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    onclick: function (evt) {
+        OpenLayers.Event.stop(evt, true);
+    },
+
+    /** 
+     * Method: onmouseout
+     * When mouse goes out of the popup set the flag to false so that
+     *   if they let go and then drag back in, we won't be confused.
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    onmouseout: function (evt) {
+        this.mousedown = false;
+    },
+    
+    /** 
+     * Method: ondblclick
+     * Ignore double-clicks, but allowing default browser handling
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    ondblclick: function (evt) {
+        OpenLayers.Event.stop(evt, true);
+    },
+
+    CLASS_NAME: "OpenLayers.Popup"
+});
+
+OpenLayers.Popup.WIDTH = 200;
+OpenLayers.Popup.HEIGHT = 200;
+OpenLayers.Popup.COLOR = "white";
+OpenLayers.Popup.OPACITY = 1;
+OpenLayers.Popup.BORDER = "0px";
+/* ======================================================================
+    OpenLayers/Protocol.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Class: OpenLayers.Protocol
+ * Abstract vector layer protocol class.  Not to be instantiated directly.  Use
+ *     one of the protocol subclasses instead.
+ */
+OpenLayers.Protocol = OpenLayers.Class({
+    
+    /**
+     * Property: format
+     * {<OpenLayers.Format>}
+     */
+    format: null,
+    
+    /**
+     * Property: options
+     * Any options sent to the constructor.
+     */
+    options: null,
+
+    /**
+     * Property: autoDestroy
+     * {Boolean} The creator of the protocol can set autoDestroy to false
+     *      to fully control when the protocol is destroyed. Defaults to
+     *      true.
+     */
+    autoDestroy: true,
+   
+    /**
+     * Constructor: OpenLayers.Protocol
+     * Abstract class for vector protocols.  Create instances of a subclass.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     */
+    initialize: function(options) {
+        options = options || {};
+        OpenLayers.Util.extend(this, options);
+        this.options = options;
+    },
+
+    /**
+     * APIMethod: destroy
+     * Clean up the protocol.
+     */
+    destroy: function() {
+        this.options = null;
+        this.format = null;
+    },
+    
+    /**
+     * Method: read
+     * Construct a request for reading new features.
+     *
+     * Parameters:
+     * options - {Object} Optional object for configuring the request.
+     *
+     * Returns:
+     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+     * object, the same object will be passed to the callback function passed
+     * if one exists in the options object.
+     */
+    read: function() {
+    },
+    
+    
+    /**
+     * Method: create
+     * Construct a request for writing newly created features.
+     *
+     * Parameters:
+     * features - {Array({<OpenLayers.Feature.Vector>})} or
+     *            {<OpenLayers.Feature.Vector>}
+     * options - {Object} Optional object for configuring the request.
+     *
+     * Returns:
+     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+     * object, the same object will be passed to the callback function passed
+     * if one exists in the options object.
+     */
+    create: function() {
+    },
+    
+    /**
+     * Method: update
+     * Construct a request updating modified features.
+     *
+     * Parameters:
+     * features - {Array({<OpenLayers.Feature.Vector>})} or
+     *            {<OpenLayers.Feature.Vector>}
+     * options - {Object} Optional object for configuring the request.
+     *
+     * Returns:
+     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+     * object, the same object will be passed to the callback function passed
+     * if one exists in the options object.
+     */
+    update: function() {
+    },
+    
+    /**
+     * Method: delete
+     * Construct a request deleting a removed feature.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     * options - {Object} Optional object for configuring the request.
+     *
+     * Returns:
+     * {<OpenLayers.Protocol.Response>} An <OpenLayers.Protocol.Response>
+     * object, the same object will be passed to the callback function passed
+     * if one exists in the options object.
+     */
+    "delete": function() {
+    },
+
+    /**
+     * Method: commit
+     * Go over the features and for each take action
+     * based on the feature state. Possible actions are create,
+     * update and delete.
+     *
+     * Parameters:
+     * features - {Array({<OpenLayers.Feature.Vector>})}
+     * options - {Object} Object whose possible keys are "create", "update",
+     *      "delete", "callback" and "scope", the values referenced by the
+     *      first three are objects as passed to the "create", "update", and
+     *      "delete" methods, the value referenced by the "callback" key is
+     *      a function which is called when the commit operation is complete
+     *      using the scope referenced by the "scope" key.
+     *
+     * Returns:
+     * {Array({<OpenLayers.Protocol.Response>})} An array of
+     * <OpenLayers.Protocol.Response> objects.
+     */
+    commit: function() {
+    },
+   
+    CLASS_NAME: "OpenLayers.Protocol" 
+});
+
+/**
+ * Class: OpenLayers.Protocol.Response
+ * Protocols return Response objects to their users.
+ */
+OpenLayers.Protocol.Response = OpenLayers.Class({
+    /**
+     * Property: code
+     * {Number} - OpenLayers.Protocol.Response.SUCCESS or
+     *            OpenLayers.Protocol.Response.FAILURE
+     */
+    code: null,
+
+    /**
+     * Property: requestType
+     * {String} The type of request this response corresponds to. Either
+     *      "create", "read", "update" or "delete".
+     */
+    requestType: null,
+
+    /**
+     * Property: last
+     * {Boolean} - true if this is the last response expected in a commit,
+     * false otherwise, defaults to true.
+     */
+    last: true,
+
+    /**
+     * Property: features
+     * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+     * The features returned in the response by the server.
+     */
+    features: null,
+
+    /**
+     * Property: reqFeatures
+     * {Array({<OpenLayers.Feature.Vector>})} or {<OpenLayers.Feature.Vector>}
+     * The features provided by the user and placed in the request by the
+     *      protocol.
+     */
+    reqFeatures: null,
+
+    /**
+     * Property: priv
+     */
+    priv: null,
+
+    /**
+     * Constructor: OpenLayers.Protocol.Response
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /**
+     * Method: success
+     *
+     * Returns:
+     * {Boolean} - true on success, false otherwise
+     */
+    success: function() {
+        return this.code > 0;
+    },
+
+    CLASS_NAME: "OpenLayers.Protocol.Response"
+});
+
+OpenLayers.Protocol.Response.SUCCESS = 1;
+OpenLayers.Protocol.Response.FAILURE = 0;
+/* ======================================================================
+    OpenLayers/Renderer.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Class: OpenLayers.Renderer 
+ * This is the base class for all renderers.
+ *
+ * This is based on a merger code written by Paul Spencer and Bertil Chapuis.
+ * It is largely composed of virtual functions that are to be implemented
+ * in technology-specific subclasses, but there is some generic code too.
+ * 
+ * The functions that *are* implemented here merely deal with the maintenance
+ *  of the size and extent variables, as well as the cached 'resolution' 
+ *  value. 
+ * 
+ * A note to the user that all subclasses should use getResolution() instead
+ *  of directly accessing this.resolution in order to correctly use the 
+ *  cacheing system.
+ *
+ */
+OpenLayers.Renderer = OpenLayers.Class({
+
+    /** 
+     * Property: container
+     * {DOMElement} 
+     */
+    container: null,
+    
+    /** 
+     * Property: extent
+     * {<OpenLayers.Bounds>}
+     */
+    extent: null,
+
+    /**
+     * Property: locked
+     * {Boolean} If the renderer is currently in a state where many things
+     *     are changing, the 'locked' property is set to true. This means 
+     *     that renderers can expect at least one more drawFeature event to be
+     *     called with the 'locked' property set to 'true': In some renderers,
+     *     this might make sense to use as a 'only update local information'
+     *     flag. 
+     */  
+    locked: false,
+    
+    /** 
+     * Property: size
+     * {<OpenLayers.Size>} 
+     */
+    size: null,
+    
+    /**
+     * Property: resolution
+     * {Float} cache of current map resolution
+     */
+    resolution: null,
+    
+    /**
+     * Property: map  
+     * {<OpenLayers.Map>} Reference to the map -- this is set in Vector's setMap()
+     */
+    map: null,
+    
+    /**
+     * Constructor: OpenLayers.Renderer 
+     *
+     * Parameters:
+     * containerID - {<String>} 
+     * options - {Object} options for this renderer. See sublcasses for
+     *     supported options.
+     */
+    initialize: function(containerID, options) {
+        this.container = OpenLayers.Util.getElement(containerID);
+    },
+    
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        this.container = null;
+        this.extent = null;
+        this.size =  null;
+        this.resolution = null;
+        this.map = null;
+    },
+
+    /**
+     * APIMethod: supported
+     * This should be overridden by specific subclasses
+     * 
+     * Returns:
+     * {Boolean} Whether or not the browser supports the renderer class
+     */
+    supported: function() {
+        return false;
+    },    
+    
+    /**
+     * Method: setExtent
+     * Set the visible part of the layer.
+     *
+     * Resolution has probably changed, so we nullify the resolution 
+     * cache (this.resolution) -- this way it will be re-computed when 
+     * next it is needed.
+     * We nullify the resolution cache (this.resolution) if resolutionChanged
+     * is set to true - this way it will be re-computed on the next
+     * getResolution() request.
+     *
+     * Parameters:
+     * extent - {<OpenLayers.Bounds>}
+     * resolutionChanged - {Boolean}
+     */
+    setExtent: function(extent, resolutionChanged) {
+        this.extent = extent.clone();
+        if (resolutionChanged) {
+            this.resolution = null;
+        }
+    },
+    
+    /**
+     * Method: setSize
+     * Sets the size of the drawing surface.
+     * 
+     * Resolution has probably changed, so we nullify the resolution 
+     * cache (this.resolution) -- this way it will be re-computed when 
+     * next it is needed.
+     *
+     * Parameters:
+     * size - {<OpenLayers.Size>} 
+     */
+    setSize: function(size) {
+        this.size = size.clone();
+        this.resolution = null;
+    },
+    
+    /** 
+     * Method: getResolution
+     * Uses cached copy of resolution if available to minimize computing
+     * 
+     * Returns:
+     * The current map's resolution
+     */
+    getResolution: function() {
+        this.resolution = this.resolution || this.map.getResolution();
+        return this.resolution;
+    },
+    
+    /**
+     * Method: drawFeature
+     * Draw the feature.  The optional style argument can be used
+     * to override the feature's own style.  This method should only
+     * be called from layer.drawFeature().
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     * style - {<Object>}
+     * 
+     * Returns:
+     * {Boolean} true if the feature has been drawn completely, false if not,
+     *     undefined if the feature had no geometry
+     */
+    drawFeature: function(feature, style) {
+        if(style == null) {
+            style = feature.style;
+        }
+        if (feature.geometry) {
+            if (!feature.geometry.getBounds().intersectsBounds(this.extent)) {
+                style = {display: "none"};
+            }
+            return this.drawGeometry(feature.geometry, style, feature.id);
+        }
+    },
+
+
+    /** 
+     * Method: drawGeometry
+     * 
+     * Draw a geometry.  This should only be called from the renderer itself.
+     * Use layer.drawFeature() from outside the renderer.
+     * virtual function
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} 
+     * style - {Object} 
+     * featureId - {<String>} 
+     */
+    drawGeometry: function(geometry, style, featureId) {},
+        
+    /**
+     * Method: clear
+     * Clear all vectors from the renderer.
+     * virtual function.
+     */    
+    clear: function() {},
+
+    /**
+     * Method: getFeatureIdFromEvent
+     * Returns a feature id from an event on the renderer.  
+     * How this happens is specific to the renderer.  This should be
+     * called from layer.getFeatureFromEvent().
+     * Virtual function.
+     * 
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     *
+     * Returns:
+     * {String} A feature id or null.
+     */
+    getFeatureIdFromEvent: function(evt) {},
+    
+    /**
+     * Method: eraseFeatures 
+     * This is called by the layer to erase features
+     * 
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    eraseFeatures: function(features) {
+        if(!(features instanceof Array)) {
+            features = [features];
+        }
+        for(var i=0, len=features.length; i<len; ++i) {
+            this.eraseGeometry(features[i].geometry);
+        }
+    },
+    
+    /**
+     * Method: eraseGeometry
+     * Remove a geometry from the renderer (by id).
+     * virtual function.
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} 
+     */
+    eraseGeometry: function(geometry) {},
+
+    CLASS_NAME: "OpenLayers.Renderer"
+});
+/* ======================================================================
+    OpenLayers/Request.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Namespace: OpenLayers.Request
+ * The OpenLayers.Request namespace contains convenience methods for working
+ *     with XMLHttpRequests.  These methods work with a cross-browser
+ *     W3C compliant <OpenLayers.Request.XMLHttpRequest> class.
+ */
+OpenLayers.Request = {
+    
+    /**
+     * Constant: DEFAULT_CONFIG
+     * {Object} Default configuration for all requests.
+     */
+    DEFAULT_CONFIG: {
+        method: "GET",
+        url: window.location.href,
+        async: true,
+        user: undefined,
+        password: undefined,
+        params: null,
+        proxy: OpenLayers.ProxyHost,
+        headers: {},
+        data: null,
+        callback: function() {},
+        success: null,
+        failure: null,
+        scope: null
+    },
+    
+    /**
+     * APIMethod: issue
+     * Create a new XMLHttpRequest object, open it, set any headers, bind
+     *     a callback to done state, and send any data.
+     *
+     * Parameters:
+     * config - {Object} Object containing properties for configuring the
+     *     request.  Allowed configuration properties are described below.
+     *     This object is modified and should not be reused.
+     *
+     * Allowed config properties:
+     * method - {String} One of GET, POST, PUT, DELETE, HEAD, or
+     *     OPTIONS.  Default is GET.
+     * url - {String} URL for the request.
+     * async - {Boolean} Open an asynchronous request.  Default is true.
+     * user - {String} User for relevant authentication scheme.  Set
+     *     to null to clear current user.
+     * password - {String} Password for relevant authentication scheme.
+     *     Set to null to clear current password.
+     * proxy - {String} Optional proxy.  Defaults to
+     *     <OpenLayers.ProxyHost>.
+     * params - {Object} Any key:value pairs to be appended to the
+     *     url as a query string.  Assumes url doesn't already include a query
+     *     string or hash.  Parameter values that are arrays will be
+     *     concatenated with a comma (note that this goes against form-encoding)
+     *     as is done with <OpenLayers.Util.getParameterString>.
+     * headers - {Object} Object with header:value pairs to be set on
+     *     the request.
+     * data - {Object} Any data to send with the request.
+     * callback - {Function} Function to call when request is done.
+     *     To determine if the request failed, check request.status (200
+     *     indicates success).
+     * success - {Function} Optional function to call if request status is in
+     *     the 200s.  This will be called in addition to callback above and
+     *     would typically only be used as an alternative.
+     * failure - {Function} Optional function to call if request status is not
+     *     in the 200s.  This will be called in addition to callback above and
+     *     would typically only be used as an alternative.
+     * scope - {Object} If callback is a public method on some object,
+     *     set the scope to that object.
+     *
+     * Returns:
+     * {XMLHttpRequest} Request object.  To abort the request before a response
+     *     is received, call abort() on the request object.
+     */
+    issue: function(config) {        
+        // apply default config - proxy host may have changed
+        var defaultConfig = OpenLayers.Util.extend(
+            this.DEFAULT_CONFIG,
+            {proxy: OpenLayers.ProxyHost}
+        );
+        config = OpenLayers.Util.applyDefaults(config, defaultConfig);
+
+        // create request, open, and set headers
+        var request = new OpenLayers.Request.XMLHttpRequest();
+        var url = config.url;
+        if(config.params) {
+            url += "?" + OpenLayers.Util.getParameterString(config.params);
+        }
+        if(config.proxy && (url.indexOf("http") == 0)) {
+            url = config.proxy + encodeURIComponent(url);
+        }
+        request.open(
+            config.method, url, config.async, config.user, config.password
+        );
+        for(var header in config.headers) {
+            request.setRequestHeader(header, config.headers[header]);
+        }
+
+        // bind callbacks to readyState 4 (done)
+        var complete = (config.scope) ?
+            OpenLayers.Function.bind(config.callback, config.scope) :
+            config.callback;
+        
+        // optional success callback
+        var success;
+        if(config.success) {
+            success = (config.scope) ?
+                OpenLayers.Function.bind(config.success, config.scope) :
+                config.success;
+        }
+
+        // optional failure callback
+        var failure;
+        if(config.failure) {
+            failure = (config.scope) ?
+                OpenLayers.Function.bind(config.failure, config.scope) :
+                config.failure;
+        }
+         
+        request.onreadystatechange = function() {
+            if(request.readyState == OpenLayers.Request.XMLHttpRequest.DONE) {
+                complete(request);
+                if(success && (!request.status ||
+                   (request.status >= 200 && request.status < 300))) {
+                    success(request);
+                }
+                if(failure && (request.status &&
+                   (request.status < 200 || request.status >= 300))) {
+                    failure(request);
+                }
+            }
+        }
+        
+        // send request (optionally with data) and return
+        request.send(config.data);
+        return request;
+    },
+    
+    /**
+     * APIMethod: GET
+     * Send an HTTP GET request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to GET.
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.
+     *     This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    GET: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "GET"});
+        return OpenLayers.Request.issue(config);
+    },
+    
+    /**
+     * APIMethod: POST
+     * Send a POST request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to POST and "Content-Type" header set to "application/xml".
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.  The
+     *     default "Content-Type" header will be set to "application-xml" if
+     *     none is provided.  This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    POST: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "POST"});
+        // set content type to application/xml if it isn't already set
+        config.headers = config.headers ? config.headers : {};
+        if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+            config.headers["Content-Type"] = "application/xml";
+        }
+        return OpenLayers.Request.issue(config);
+    },
+    
+    /**
+     * APIMethod: PUT
+     * Send an HTTP PUT request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to PUT and "Content-Type" header set to "application/xml".
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.  The
+     *     default "Content-Type" header will be set to "application-xml" if
+     *     none is provided.  This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    PUT: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "PUT"});
+        // set content type to application/xml if it isn't already set
+        config.headers = config.headers ? config.headers : {};
+        if(!("CONTENT-TYPE" in OpenLayers.Util.upperCaseObject(config.headers))) {
+            config.headers["Content-Type"] = "application/xml";
+        }
+        return OpenLayers.Request.issue(config);
+    },
+    
+    /**
+     * APIMethod: DELETE
+     * Send an HTTP DELETE request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to DELETE.
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.
+     *     This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    DELETE: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "DELETE"});
+        return OpenLayers.Request.issue(config);
+    },
+  
+    /**
+     * APIMethod: HEAD
+     * Send an HTTP HEAD request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to HEAD.
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.
+     *     This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    HEAD: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "HEAD"});
+        return OpenLayers.Request.issue(config);
+    },
+    
+    /**
+     * APIMethod: OPTIONS
+     * Send an HTTP OPTIONS request.  Additional configuration properties are
+     *     documented in the <issue> method, with the method property set
+     *     to OPTIONS.
+     *
+     * Parameters:
+     * config - {Object} Object with properties for configuring the request.
+     *     See the <issue> method for documentation of allowed properties.
+     *     This object is modified and should not be reused.
+     * 
+     * Returns:
+     * {XMLHttpRequest} Request object.
+     */
+    OPTIONS: function(config) {
+        config = OpenLayers.Util.extend(config, {method: "OPTIONS"});
+        return OpenLayers.Request.issue(config);
+    }
+
+};
+/* ======================================================================
+    OpenLayers/Strategy.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Class: OpenLayers.Strategy
+ * Abstract vector layer strategy class.  Not to be instantiated directly.  Use
+ *     one of the strategy subclasses instead.
+ */
+OpenLayers.Strategy = OpenLayers.Class({
+    
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>}
+     */
+    layer: null,
+    
+    /**
+     * Property: options
+     * {Object} Any options sent to the constructor.
+     */
+    options: null,
+
+    /** 
+     * Property: active 
+     * {Boolean} The control is active.
+     */
+    active: null,
+
+    /**
+     * Property: autoActivate
+     * {Boolean} The creator of the strategy can set autoActivate to false
+     *      to fully control when the protocol is activated and deactivated.
+     *      Defaults to true.
+     */
+    autoActivate: true,
+
+    /**
+     * Property: autoDestroy
+     * {Boolean} The creator of the strategy can set autoDestroy to false
+     *      to fully control when the strategy is destroyed. Defaults to
+     *      true.
+     */
+    autoDestroy: true,
+
+    /**
+     * Constructor: OpenLayers.Strategy
+     * Abstract class for vector strategies.  Create instances of a subclass.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Util.extend(this, options);
+        this.options = options;
+        // set the active property here, so that user cannot override it
+        this.active = false;
+    },
+    
+    /**
+     * APIMethod: destroy
+     * Clean up the strategy.
+     */
+    destroy: function() {
+        this.deactivate();
+        this.layer = null;
+        this.options = null;
+    },
+
+    /**
+     * Method: setLayer.
+     *
+     * Parameters:
+     * {<OpenLayers.Layer.Vector>}
+     */
+    setLayer: function(layer) {
+        this.layer = layer;
+    },
+    
+    /**
+     * Method: activate
+     * Activate the strategy.  Register any listeners, do appropriate setup.
+     *
+     * Returns:
+     * {Boolean} True if the strategy was successfully activated or false if
+     *      the strategy was already active.
+     */
+    activate: function() {
+        if (!this.active) {
+            this.active = true;
+            return true;
+        }
+        return false;
+    },
+    
+    /**
+     * Method: deactivate
+     * Deactivate the strategy.  Unregister any listeners, do appropriate
+     *     tear-down.
+     *
+     * Returns:
+     * {Boolean} True if the strategy was successfully deactivated or false if
+     *      the strategy was already inactive.
+     */
+    deactivate: function() {
+        if (this.active) {
+            this.active = false;
+            return true;
+        }
+        return false;
+    },
+   
+    CLASS_NAME: "OpenLayers.Strategy" 
+});
+/* ======================================================================
     OpenLayers/Tween.js
    ====================================================================== */
 
@@ -5422,6 +7131,254 @@
     CLASS_NAME: "OpenLayers.Easing.Quad"
 };
 /* ======================================================================
+    Rico/Color.js
+   ====================================================================== */
+
+/*
+ * This file has been edited substantially from the Rico-released version by
+ * the OpenLayers development team.
+ *
+ * This file is licensed under the Apache License, Version 2.0.
+ */
+OpenLayers.Rico.Color = OpenLayers.Class({
+
+   initialize: function(red, green, blue) {
+      this.rgb = { r: red, g : green, b : blue };
+   },
+
+   setRed: function(r) {
+      this.rgb.r = r;
+   },
+
+   setGreen: function(g) {
+      this.rgb.g = g;
+   },
+
+   setBlue: function(b) {
+      this.rgb.b = b;
+   },
+
+   setHue: function(h) {
+
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.h = h;
+
+      // convert back to RGB...
+      this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+   },
+
+   setSaturation: function(s) {
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.s = s;
+
+      // convert back to RGB and set values...
+      this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, hsb.b);
+   },
+
+   setBrightness: function(b) {
+      // get an HSB model, and set the new hue...
+      var hsb = this.asHSB();
+      hsb.b = b;
+
+      // convert back to RGB and set values...
+      this.rgb = OpenLayers.Rico.Color.HSBtoRGB( hsb.h, hsb.s, hsb.b );
+   },
+
+   darken: function(percent) {
+      var hsb  = this.asHSB();
+      this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.max(hsb.b - percent,0));
+   },
+
+   brighten: function(percent) {
+      var hsb  = this.asHSB();
+      this.rgb = OpenLayers.Rico.Color.HSBtoRGB(hsb.h, hsb.s, Math.min(hsb.b + percent,1));
+   },
+
+   blend: function(other) {
+      this.rgb.r = Math.floor((this.rgb.r + other.rgb.r)/2);
+      this.rgb.g = Math.floor((this.rgb.g + other.rgb.g)/2);
+      this.rgb.b = Math.floor((this.rgb.b + other.rgb.b)/2);
+   },
+
+   isBright: function() {
+      var hsb = this.asHSB();
+      return this.asHSB().b > 0.5;
+   },
+
+   isDark: function() {
+      return ! this.isBright();
+   },
+
+   asRGB: function() {
+      return "rgb(" + this.rgb.r + "," + this.rgb.g + "," + this.rgb.b + ")";
+   },
+
+   asHex: function() {
+      return "#" + this.rgb.r.toColorPart() + this.rgb.g.toColorPart() + this.rgb.b.toColorPart();
+   },
+
+   asHSB: function() {
+      return OpenLayers.Rico.Color.RGBtoHSB(this.rgb.r, this.rgb.g, this.rgb.b);
+   },
+
+   toString: function() {
+      return this.asHex();
+   }
+
+});
+
+OpenLayers.Rico.Color.createFromHex = function(hexCode) {
+  if(hexCode.length==4) {
+    var shortHexCode = hexCode; 
+    var hexCode = '#';
+    for(var i=1;i<4;i++) { 
+        hexCode += (shortHexCode.charAt(i) + 
+shortHexCode.charAt(i)); 
+    }
+  }
+   if ( hexCode.indexOf('#') == 0 ) {
+       hexCode = hexCode.substring(1);
+   }
+   var red   = hexCode.substring(0,2);
+   var green = hexCode.substring(2,4);
+   var blue  = hexCode.substring(4,6);
+   return new OpenLayers.Rico.Color( parseInt(red,16), parseInt(green,16), parseInt(blue,16) );
+};
+
+/**
+ * Factory method for creating a color from the background of
+ * an HTML element.
+ */
+OpenLayers.Rico.Color.createColorFromBackground = function(elem) {
+
+   var actualColor = 
+      RicoUtil.getElementsComputedStyle(OpenLayers.Util.getElement(elem), 
+                                        "backgroundColor", 
+                                        "background-color");
+
+   if ( actualColor == "transparent" && elem.parentNode ) {
+      return OpenLayers.Rico.Color.createColorFromBackground(elem.parentNode);
+   }
+   if ( actualColor == null ) {
+      return new OpenLayers.Rico.Color(255,255,255);
+   }
+   if ( actualColor.indexOf("rgb(") == 0 ) {
+      var colors = actualColor.substring(4, actualColor.length - 1 );
+      var colorArray = colors.split(",");
+      return new OpenLayers.Rico.Color( parseInt( colorArray[0] ),
+                            parseInt( colorArray[1] ),
+                            parseInt( colorArray[2] )  );
+
+   }
+   else if ( actualColor.indexOf("#") == 0 ) {
+      return OpenLayers.Rico.Color.createFromHex(actualColor);
+   }
+   else {
+      return new OpenLayers.Rico.Color(255,255,255);
+   }
+};
+
+OpenLayers.Rico.Color.HSBtoRGB = function(hue, saturation, brightness) {
+
+   var red   = 0;
+    var green = 0;
+    var blue  = 0;
+
+   if (saturation == 0) {
+      red = parseInt(brightness * 255.0 + 0.5);
+       green = red;
+       blue = red;
+    }
+    else {
+      var h = (hue - Math.floor(hue)) * 6.0;
+      var f = h - Math.floor(h);
+      var p = brightness * (1.0 - saturation);
+      var q = brightness * (1.0 - saturation * f);
+      var t = brightness * (1.0 - (saturation * (1.0 - f)));
+
+      switch (parseInt(h)) {
+         case 0:
+            red   = (brightness * 255.0 + 0.5);
+            green = (t * 255.0 + 0.5);
+            blue  = (p * 255.0 + 0.5);
+            break;
+         case 1:
+            red   = (q * 255.0 + 0.5);
+            green = (brightness * 255.0 + 0.5);
+            blue  = (p * 255.0 + 0.5);
+            break;
+         case 2:
+            red   = (p * 255.0 + 0.5);
+            green = (brightness * 255.0 + 0.5);
+            blue  = (t * 255.0 + 0.5);
+            break;
+         case 3:
+            red   = (p * 255.0 + 0.5);
+            green = (q * 255.0 + 0.5);
+            blue  = (brightness * 255.0 + 0.5);
+            break;
+         case 4:
+            red   = (t * 255.0 + 0.5);
+            green = (p * 255.0 + 0.5);
+            blue  = (brightness * 255.0 + 0.5);
+            break;
+          case 5:
+            red   = (brightness * 255.0 + 0.5);
+            green = (p * 255.0 + 0.5);
+            blue  = (q * 255.0 + 0.5);
+            break;
+        }
+    }
+
+   return { r : parseInt(red), g : parseInt(green) , b : parseInt(blue) };
+};
+
+OpenLayers.Rico.Color.RGBtoHSB = function(r, g, b) {
+
+   var hue;
+   var saturation;
+   var brightness;
+
+   var cmax = (r > g) ? r : g;
+   if (b > cmax) {
+      cmax = b;
+   }
+   var cmin = (r < g) ? r : g;
+   if (b < cmin) {
+      cmin = b;
+   }
+   brightness = cmax / 255.0;
+   if (cmax != 0) {
+      saturation = (cmax - cmin)/cmax;
+   } else {
+      saturation = 0;
+   }
+   if (saturation == 0) {
+      hue = 0;
+   } else {
+      var redc   = (cmax - r)/(cmax - cmin);
+        var greenc = (cmax - g)/(cmax - cmin);
+        var bluec  = (cmax - b)/(cmax - cmin);
+
+        if (r == cmax) {
+           hue = bluec - greenc;
+        } else if (g == cmax) {
+           hue = 2.0 + redc - bluec;
+      } else {
+           hue = 4.0 + greenc - redc;
+      }
+        hue = hue / 6.0;
+        if (hue < 0) {
+           hue = hue + 1.0;
+        }
+   }
+
+   return { h : hue, s : saturation, b : brightness };
+};
+
+/* ======================================================================
     OpenLayers/Control/ArgParser.js
    ====================================================================== */
 
@@ -5495,7 +7452,7 @@
         OpenLayers.Control.prototype.setMap.apply(this, arguments);
 
         //make sure we dont already have an arg parser attached
-        for(var i=0; i< this.map.controls.length; i++) {
+        for(var i=0, len=this.map.controls.length; i<len; i++) {
             var control = this.map.controls[i];
             if ( (control != this) &&
                  (control.CLASS_NAME == "OpenLayers.Control.ArgParser") ) {
@@ -5568,7 +7525,7 @@
         if (this.layers.length == this.map.layers.length) { 
             this.map.events.unregister('addlayer', this, this.configureLayers);
 
-            for(var i=0; i < this.layers.length; i++) {
+            for(var i=0, len=this.layers.length; i<len; i++) {
                 
                 var layer = this.map.layers[i];
                 var c = this.layers.charAt(i);
@@ -5585,6 +7542,1145 @@
     CLASS_NAME: "OpenLayers.Control.ArgParser"
 });
 /* ======================================================================
+    OpenLayers/Control/Attribution.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Attribution
+ * Add attribution from layers to the map display. Uses 'attribution' property
+ * of each layer.
+ */
+OpenLayers.Control.Attribution = 
+  OpenLayers.Class(OpenLayers.Control, {
+    
+    /**
+     * APIProperty: seperator
+     * {String} String used to seperate layers.
+     */
+    separator: ", ",
+       
+    /**
+     * Constructor: OpenLayers.Control.Attribution 
+     * 
+     * Parameters:
+     * options - {Object} Options for control.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, arguments);
+    },
+
+    /** 
+     * Method: destroy
+     * Destroy control.
+     */
+    destroy: function() {
+        this.map.events.un({
+            "removelayer": this.updateAttribution,
+            "addlayer": this.updateAttribution,
+            "changelayer": this.updateAttribution,
+            "changebaselayer": this.updateAttribution,
+            scope: this
+        });
+        
+        OpenLayers.Control.prototype.destroy.apply(this, arguments);
+    },    
+    
+    /**
+     * Method: draw
+     * Initialize control.
+     * 
+     * Returns: 
+     * {DOMElement} A reference to the DIV DOMElement containing the control
+     */    
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        
+        this.map.events.on({
+            'changebaselayer': this.updateAttribution,
+            'changelayer': this.updateAttribution,
+            'addlayer': this.updateAttribution,
+            'removelayer': this.updateAttribution,
+            scope: this
+        });
+        this.updateAttribution();
+        
+        return this.div;    
+    },
+
+    /**
+     * Method: updateAttribution
+     * Update attribution string.
+     */
+    updateAttribution: function() {
+        var attributions = [];
+        if (this.map && this.map.layers) {
+            for(var i=0, len=this.map.layers.length; i<len; i++) {
+                var layer = this.map.layers[i];
+                if (layer.attribution && layer.getVisibility()) {
+                    attributions.push( layer.attribution );
+                }
+            }  
+            this.div.innerHTML = attributions.join(this.separator);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Control.Attribution"
+});
+/* ======================================================================
+    OpenLayers/Control/Button.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Button 
+ * A very simple button controlfor use with <OpenLayers.Control.Panel>.
+ * When clicked, the function trigger() is executed.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ *
+ * Use:
+ * (code)
+ * var button = new OpenLayers.Control.Button({
+ *     displayClass: "MyButton", trigger: myFunction
+ * });
+ * panel.addControls([button]);
+ * (end)
+ * 
+ * Will create a button with CSS class MyButtonItemInactive, that
+ *     will call the function MyFunction() when clicked.
+ */
+OpenLayers.Control.Button = OpenLayers.Class(OpenLayers.Control, {
+    /**
+     * Property: type
+     * {Integer} OpenLayers.Control.TYPE_BUTTON.
+     */
+    type: OpenLayers.Control.TYPE_BUTTON,
+    
+    /**
+     * Method: trigger
+     * Called by a control panel when the button is clicked.
+     */
+    trigger: function() {},
+
+    CLASS_NAME: "OpenLayers.Control.Button"
+});
+/* ======================================================================
+    OpenLayers/Control/LayerSwitcher.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/** 
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.LayerSwitcher
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.LayerSwitcher = 
+  OpenLayers.Class(OpenLayers.Control, {
+
+    /**  
+     * Property: activeColor
+     * {String}
+     */
+    activeColor: "darkblue",
+    
+    /**  
+     * Property: layerStates 
+     * {Array(Object)} Basically a copy of the "state" of the map's layers 
+     *     the last time the control was drawn. We have this in order to avoid
+     *     unnecessarily redrawing the control.
+     */
+    layerStates: null,
+    
+
+  // DOM Elements
+  
+    /**
+     * Property: layersDiv
+     * {DOMElement} 
+     */
+    layersDiv: null,
+    
+    /** 
+     * Property: baseLayersDiv
+     * {DOMElement}
+     */
+    baseLayersDiv: null,
+
+    /** 
+     * Property: baseLayers
+     * {Array(<OpenLayers.Layer>)}
+     */
+    baseLayers: null,
+    
+    
+    /** 
+     * Property: dataLbl
+     * {DOMElement} 
+     */
+    dataLbl: null,
+    
+    /** 
+     * Property: dataLayersDiv
+     * {DOMElement} 
+     */
+    dataLayersDiv: null,
+
+    /** 
+     * Property: dataLayers
+     * {Array(<OpenLayers.Layer>)} 
+     */
+    dataLayers: null,
+
+
+    /** 
+     * Property: minimizeDiv
+     * {DOMElement} 
+     */
+    minimizeDiv: null,
+
+    /** 
+     * Property: maximizeDiv
+     * {DOMElement} 
+     */
+    maximizeDiv: null,
+    
+    /**
+     * APIProperty: ascending
+     * {Boolean} 
+     */
+    ascending: true,
+ 
+    /**
+     * Constructor: OpenLayers.Control.LayerSwitcher
+     * 
+     * Parameters:
+     * options - {Object}
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, arguments);
+        this.layerStates = [];
+    },
+
+    /**
+     * APIMethod: destroy 
+     */    
+    destroy: function() {
+        
+        OpenLayers.Event.stopObservingElement(this.div);
+
+        OpenLayers.Event.stopObservingElement(this.minimizeDiv);
+        OpenLayers.Event.stopObservingElement(this.maximizeDiv);
+
+        //clear out layers info and unregister their events 
+        this.clearLayersArray("base");
+        this.clearLayersArray("data");
+        
+        this.map.events.un({
+            "addlayer": this.redraw,
+            "changelayer": this.redraw,
+            "removelayer": this.redraw,
+            "changebaselayer": this.redraw,
+            scope: this
+        });
+        
+        OpenLayers.Control.prototype.destroy.apply(this, arguments);
+    },
+
+    /** 
+     * Method: setMap
+     *
+     * Properties:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+        this.map.events.on({
+            "addlayer": this.redraw,
+            "changelayer": this.redraw,
+            "removelayer": this.redraw,
+            "changebaselayer": this.redraw,
+            scope: this
+        });
+    },
+
+    /**
+     * Method: draw
+     *
+     * Returns:
+     * {DOMElement} A reference to the DIV DOMElement containing the 
+     *     switcher tabs.
+     */  
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this);
+
+        // create layout divs
+        this.loadContents();
+
+        // set mode to minimize
+        if(!this.outsideViewport) {
+            this.minimizeControl();
+        }
+
+        // populate div with current info
+        this.redraw();    
+
+        return this.div;
+    },
+
+    /** 
+     * Method: clearLayersArray
+     * User specifies either "base" or "data". we then clear all the
+     *     corresponding listeners, the div, and reinitialize a new array.
+     * 
+     * Parameters:
+     * layersType - {String}  
+     */
+    clearLayersArray: function(layersType) {
+        var layers = this[layersType + "Layers"];
+        if (layers) {
+            for(var i=0, len=layers.length; i<len ; i++) {
+                var layer = layers[i];
+                OpenLayers.Event.stopObservingElement(layer.inputElem);
+                OpenLayers.Event.stopObservingElement(layer.labelSpan);
+            }
+        }
+        this[layersType + "LayersDiv"].innerHTML = "";
+        this[layersType + "Layers"] = [];
+    },
+
+
+    /**
+     * Method: checkRedraw
+     * Checks if the layer state has changed since the last redraw() call.
+     * 
+     * Returns:
+     * {Boolean} The layer state changed since the last redraw() call. 
+     */
+    checkRedraw: function() {
+        var redraw = false;
+        if ( !this.layerStates.length ||
+             (this.map.layers.length != this.layerStates.length) ) {
+            redraw = true;
+        } else {
+            for (var i=0, len=this.layerStates.length; i<len; i++) {
+                var layerState = this.layerStates[i];
+                var layer = this.map.layers[i];
+                if ( (layerState.name != layer.name) || 
+                     (layerState.inRange != layer.inRange) || 
+                     (layerState.id != layer.id) || 
+                     (layerState.visibility != layer.visibility) ) {
+                    redraw = true;
+                    break;
+                }    
+            }
+        }    
+        return redraw;
+    },
+    
+    /** 
+     * Method: redraw
+     * Goes through and takes the current state of the Map and rebuilds the
+     *     control to display that state. Groups base layers into a 
+     *     radio-button group and lists each data layer with a checkbox.
+     *
+     * Returns: 
+     * {DOMElement} A reference to the DIV DOMElement containing the control
+     */  
+    redraw: function() {
+        //if the state hasn't changed since last redraw, no need 
+        // to do anything. Just return the existing div.
+        if (!this.checkRedraw()) { 
+            return this.div; 
+        } 
+
+        //clear out previous layers 
+        this.clearLayersArray("base");
+        this.clearLayersArray("data");
+        
+        var containsOverlays = false;
+        var containsBaseLayers = false;
+        
+        // Save state -- for checking layer if the map state changed.
+        // We save this before redrawing, because in the process of redrawing
+        // we will trigger more visibility changes, and we want to not redraw
+        // and enter an infinite loop.
+        var len = this.map.layers.length;
+        this.layerStates = new Array(len);
+        for (var i=0; i <len; i++) {
+            var layer = this.map.layers[i];
+            this.layerStates[i] = {
+                'name': layer.name, 
+                'visibility': layer.visibility,
+                'inRange': layer.inRange,
+                'id': layer.id
+            };
+        }    
+
+        var layers = this.map.layers.slice();
+        if (!this.ascending) { layers.reverse(); }
+        for(var i=0, len=layers.length; i<len; i++) {
+            var layer = layers[i];
+            var baseLayer = layer.isBaseLayer;
+
+            if (layer.displayInLayerSwitcher) {
+
+                if (baseLayer) {
+                    containsBaseLayers = true;
+                } else {
+                    containsOverlays = true;
+                }    
+
+                // only check a baselayer if it is *the* baselayer, check data
+                //  layers if they are visible
+                var checked = (baseLayer) ? (layer == this.map.baseLayer)
+                                          : layer.getVisibility();
+    
+                // create input element
+                var inputElem = document.createElement("input");
+                inputElem.id = this.id + "_input_" + layer.name;
+                inputElem.name = (baseLayer) ? "baseLayers" : layer.name;
+                inputElem.type = (baseLayer) ? "radio" : "checkbox";
+                inputElem.value = layer.name;
+                inputElem.checked = checked;
+                inputElem.defaultChecked = checked;
+
+                if (!baseLayer && !layer.inRange) {
+                    inputElem.disabled = true;
+                }
+                var context = {
+                    'inputElem': inputElem,
+                    'layer': layer,
+                    'layerSwitcher': this
+                };
+                OpenLayers.Event.observe(inputElem, "mouseup", 
+                    OpenLayers.Function.bindAsEventListener(this.onInputClick,
+                                                            context)
+                );
+                
+                // create span
+                var labelSpan = document.createElement("span");
+                if (!baseLayer && !layer.inRange) {
+                    labelSpan.style.color = "gray";
+                }
+                labelSpan.innerHTML = layer.name;
+                labelSpan.style.verticalAlign = (baseLayer) ? "bottom" 
+                                                            : "baseline";
+                OpenLayers.Event.observe(labelSpan, "click", 
+                    OpenLayers.Function.bindAsEventListener(this.onInputClick,
+                                                            context)
+                );
+                // create line break
+                var br = document.createElement("br");
+    
+                
+                var groupArray = (baseLayer) ? this.baseLayers
+                                             : this.dataLayers;
+                groupArray.push({
+                    'layer': layer,
+                    'inputElem': inputElem,
+                    'labelSpan': labelSpan
+                });
+                                                     
+    
+                var groupDiv = (baseLayer) ? this.baseLayersDiv
+                                           : this.dataLayersDiv;
+                groupDiv.appendChild(inputElem);
+                groupDiv.appendChild(labelSpan);
+                groupDiv.appendChild(br);
+            }
+        }
+
+        // if no overlays, dont display the overlay label
+        this.dataLbl.style.display = (containsOverlays) ? "" : "none";        
+        
+        // if no baselayers, dont display the baselayer label
+        this.baseLbl.style.display = (containsBaseLayers) ? "" : "none";        
+
+        return this.div;
+    },
+
+    /** 
+     * Method:
+     * A label has been clicked, check or uncheck its corresponding input
+     * 
+     * Parameters:
+     * e - {Event} 
+     *
+     * Context:  
+     *  - {DOMElement} inputElem
+     *  - {<OpenLayers.Control.LayerSwitcher>} layerSwitcher
+     *  - {<OpenLayers.Layer>} layer
+     */
+
+    onInputClick: function(e) {
+
+        if (!this.inputElem.disabled) {
+            if (this.inputElem.type == "radio") {
+                this.inputElem.checked = true;
+                this.layer.map.setBaseLayer(this.layer);
+            } else {
+                this.inputElem.checked = !this.inputElem.checked;
+                this.layerSwitcher.updateMap();
+            }
+        }
+        OpenLayers.Event.stop(e);
+    },
+    
+    /**
+     * Method: onLayerClick
+     * Need to update the map accordingly whenever user clicks in either of
+     *     the layers.
+     * 
+     * Parameters: 
+     * e - {Event} 
+     */
+    onLayerClick: function(e) {
+        this.updateMap();
+    },
+
+
+    /** 
+     * Method: updateMap
+     * Cycles through the loaded data and base layer input arrays and makes
+     *     the necessary calls to the Map object such that that the map's 
+     *     visual state corresponds to what the user has selected in 
+     *     the control.
+     */
+    updateMap: function() {
+
+        // set the newly selected base layer        
+        for(var i=0, len=this.baseLayers.length; i<len; i++) {
+            var layerEntry = this.baseLayers[i];
+            if (layerEntry.inputElem.checked) {
+                this.map.setBaseLayer(layerEntry.layer, false);
+            }
+        }
+
+        // set the correct visibilities for the overlays
+        for(var i=0, len=this.dataLayers.length; i<len; i++) {
+            var layerEntry = this.dataLayers[i];   
+            layerEntry.layer.setVisibility(layerEntry.inputElem.checked);
+        }
+
+    },
+
+    /** 
+     * Method: maximizeControl
+     * Set up the labels and divs for the control
+     * 
+     * Parameters:
+     * e - {Event} 
+     */
+    maximizeControl: function(e) {
+
+        //HACK HACK HACK - find a way to auto-size this layerswitcher
+        this.div.style.width = "20em";
+        this.div.style.height = "";
+
+        this.showControls(false);
+
+        if (e != null) {
+            OpenLayers.Event.stop(e);                                            
+        }
+    },
+    
+    /** 
+     * Method: minimizeControl
+     * Hide all the contents of the control, shrink the size, 
+     *     add the maximize icon
+     *
+     * Parameters:
+     * e - {Event} 
+     */
+    minimizeControl: function(e) {
+
+        this.div.style.width = "0px";
+        this.div.style.height = "0px";
+
+        this.showControls(true);
+
+        if (e != null) {
+            OpenLayers.Event.stop(e);                                            
+        }
+    },
+
+    /**
+     * Method: showControls
+     * Hide/Show all LayerSwitcher controls depending on whether we are
+     *     minimized or not
+     * 
+     * Parameters:
+     * minimize - {Boolean}
+     */
+    showControls: function(minimize) {
+
+        this.maximizeDiv.style.display = minimize ? "" : "none";
+        this.minimizeDiv.style.display = minimize ? "none" : "";
+
+        this.layersDiv.style.display = minimize ? "none" : "";
+    },
+    
+    /** 
+     * Method: loadContents
+     * Set up the labels and divs for the control
+     */
+    loadContents: function() {
+
+        //configure main div
+        this.div.style.position = "absolute";
+        this.div.style.top = "25px";
+        this.div.style.right = "0px";
+        this.div.style.left = "";
+        this.div.style.fontFamily = "sans-serif";
+        this.div.style.fontWeight = "bold";
+        this.div.style.marginTop = "3px";
+        this.div.style.marginLeft = "3px";
+        this.div.style.marginBottom = "3px";
+        this.div.style.fontSize = "smaller";   
+        this.div.style.color = "white";   
+        this.div.style.backgroundColor = "transparent";
+    
+        OpenLayers.Event.observe(this.div, "mouseup", 
+            OpenLayers.Function.bindAsEventListener(this.mouseUp, this));
+        OpenLayers.Event.observe(this.div, "click",
+                      this.ignoreEvent);
+        OpenLayers.Event.observe(this.div, "mousedown",
+            OpenLayers.Function.bindAsEventListener(this.mouseDown, this));
+        OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);
+
+
+        // layers list div        
+        this.layersDiv = document.createElement("div");
+        this.layersDiv.id = this.id + "_layersDiv";
+        this.layersDiv.style.paddingTop = "5px";
+        this.layersDiv.style.paddingLeft = "10px";
+        this.layersDiv.style.paddingBottom = "5px";
+        this.layersDiv.style.paddingRight = "75px";
+        this.layersDiv.style.backgroundColor = this.activeColor;        
+
+        // had to set width/height to get transparency in IE to work.
+        // thanks -- http://jszen.blogspot.com/2005/04/ie6-opacity-filter-caveat.html
+        //
+        this.layersDiv.style.width = "100%";
+        this.layersDiv.style.height = "100%";
+
+
+        this.baseLbl = document.createElement("div");
+        this.baseLbl.innerHTML = OpenLayers.i18n("baseLayer");
+        this.baseLbl.style.marginTop = "3px";
+        this.baseLbl.style.marginLeft = "3px";
+        this.baseLbl.style.marginBottom = "3px";
+        
+        this.baseLayersDiv = document.createElement("div");
+        this.baseLayersDiv.style.paddingLeft = "10px";
+        /*OpenLayers.Event.observe(this.baseLayersDiv, "click", 
+            OpenLayers.Function.bindAsEventListener(this.onLayerClick, this));
+        */
+                     
+
+        this.dataLbl = document.createElement("div");
+        this.dataLbl.innerHTML = OpenLayers.i18n("overlays");
+        this.dataLbl.style.marginTop = "3px";
+        this.dataLbl.style.marginLeft = "3px";
+        this.dataLbl.style.marginBottom = "3px";
+        
+        this.dataLayersDiv = document.createElement("div");
+        this.dataLayersDiv.style.paddingLeft = "10px";
+
+        if (this.ascending) {
+            this.layersDiv.appendChild(this.baseLbl);
+            this.layersDiv.appendChild(this.baseLayersDiv);
+            this.layersDiv.appendChild(this.dataLbl);
+            this.layersDiv.appendChild(this.dataLayersDiv);
+        } else {
+            this.layersDiv.appendChild(this.dataLbl);
+            this.layersDiv.appendChild(this.dataLayersDiv);
+            this.layersDiv.appendChild(this.baseLbl);
+            this.layersDiv.appendChild(this.baseLayersDiv);
+        }    
+ 
+        this.div.appendChild(this.layersDiv);
+
+        OpenLayers.Rico.Corner.round(this.div, {corners: "tl bl",
+                                        bgColor: "transparent",
+                                        color: this.activeColor,
+                                        blend: false});
+
+        OpenLayers.Rico.Corner.changeOpacity(this.layersDiv, 0.75);
+
+        var imgLocation = OpenLayers.Util.getImagesLocation();
+        var sz = new OpenLayers.Size(18,18);        
+
+        // maximize button div
+        var img = imgLocation + 'layer-switcher-maximize.png';
+        this.maximizeDiv = OpenLayers.Util.createAlphaImageDiv(
+                                    "OpenLayers_Control_MaximizeDiv", 
+                                    null, 
+                                    sz, 
+                                    img, 
+                                    "absolute");
+        this.maximizeDiv.style.top = "5px";
+        this.maximizeDiv.style.right = "0px";
+        this.maximizeDiv.style.left = "";
+        this.maximizeDiv.style.display = "none";
+        OpenLayers.Event.observe(this.maximizeDiv, "click", 
+            OpenLayers.Function.bindAsEventListener(this.maximizeControl, this)
+        );
+        
+        this.div.appendChild(this.maximizeDiv);
+
+        // minimize button div
+        var img = imgLocation + 'layer-switcher-minimize.png';
+        var sz = new OpenLayers.Size(18,18);        
+        this.minimizeDiv = OpenLayers.Util.createAlphaImageDiv(
+                                    "OpenLayers_Control_MinimizeDiv", 
+                                    null, 
+                                    sz, 
+                                    img, 
+                                    "absolute");
+        this.minimizeDiv.style.top = "5px";
+        this.minimizeDiv.style.right = "0px";
+        this.minimizeDiv.style.left = "";
+        this.minimizeDiv.style.display = "none";
+        OpenLayers.Event.observe(this.minimizeDiv, "click", 
+            OpenLayers.Function.bindAsEventListener(this.minimizeControl, this)
+        );
+
+        this.div.appendChild(this.minimizeDiv);
+    },
+    
+    /** 
+     * Method: ignoreEvent
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    ignoreEvent: function(evt) {
+        OpenLayers.Event.stop(evt);
+    },
+
+    /** 
+     * Method: mouseDown
+     * Register a local 'mouseDown' flag so that we'll know whether or not
+     *     to ignore a mouseUp event
+     * 
+     * Parameters:
+     * evt - {Event}
+     */
+    mouseDown: function(evt) {
+        this.isMouseDown = true;
+        this.ignoreEvent(evt);
+    },
+
+    /** 
+     * Method: mouseUp
+     * If the 'isMouseDown' flag has been set, that means that the drag was 
+     *     started from within the LayerSwitcher control, and thus we can 
+     *     ignore the mouseup. Otherwise, let the Event continue.
+     *  
+     * Parameters:
+     * evt - {Event} 
+     */
+    mouseUp: function(evt) {
+        if (this.isMouseDown) {
+            this.isMouseDown = false;
+            this.ignoreEvent(evt);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Control.LayerSwitcher"
+});
+/* ======================================================================
+    OpenLayers/Control/MouseDefaults.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.MouseDefaults
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.MouseDefaults = OpenLayers.Class(OpenLayers.Control, {
+
+    /** WARNING WARNING WARNING!!!
+        This class is DEPRECATED in 2.4 and will be removed by 3.0.
+        If you need this functionality, use Control.Navigation instead!!! */
+
+    /** 
+     * Property: performedDrag
+     * {Boolean}
+     */
+    performedDrag: false,
+
+    /** 
+     * Property: wheelObserver 
+     * {Function}
+     */
+    wheelObserver: null,
+
+    /** 
+     * Constructor: OpenLayers.Control.MouseDefaults
+     */
+    initialize: function() {
+        OpenLayers.Control.prototype.initialize.apply(this, arguments);
+    },
+
+    /**
+     * APIMethod: destroy
+     */    
+    destroy: function() {
+        
+        if (this.handler) {
+            this.handler.destroy();
+        }
+        this.handler = null;
+
+        this.map.events.un({
+            "click": this.defaultClick,
+            "dblclick": this.defaultDblClick,
+            "mousedown": this.defaultMouseDown,
+            "mouseup": this.defaultMouseUp,
+            "mousemove": this.defaultMouseMove,
+            "mouseout": this.defaultMouseOut,
+            scope: this
+        });
+
+        //unregister mousewheel events specifically on the window and document
+        OpenLayers.Event.stopObserving(window, "DOMMouseScroll", 
+                                        this.wheelObserver);
+        OpenLayers.Event.stopObserving(window, "mousewheel", 
+                                        this.wheelObserver);
+        OpenLayers.Event.stopObserving(document, "mousewheel", 
+                                        this.wheelObserver);
+        this.wheelObserver = null;
+                      
+        OpenLayers.Control.prototype.destroy.apply(this, arguments);        
+    },
+
+    /**
+     * Method: draw
+     */
+    draw: function() {
+        this.map.events.on({
+            "click": this.defaultClick,
+            "dblclick": this.defaultDblClick,
+            "mousedown": this.defaultMouseDown,
+            "mouseup": this.defaultMouseUp,
+            "mousemove": this.defaultMouseMove,
+            "mouseout": this.defaultMouseOut,
+            scope: this
+        });
+
+        this.registerWheelEvents();
+
+    },
+
+    /**
+     * Method: registerWheelEvents
+     */
+    registerWheelEvents: function() {
+
+        this.wheelObserver = OpenLayers.Function.bindAsEventListener(
+            this.onWheelEvent, this
+        );
+        
+        //register mousewheel events specifically on the window and document
+        OpenLayers.Event.observe(window, "DOMMouseScroll", this.wheelObserver);
+        OpenLayers.Event.observe(window, "mousewheel", this.wheelObserver);
+        OpenLayers.Event.observe(document, "mousewheel", this.wheelObserver);
+    },
+
+    /**
+     * Method: defaultClick
+     * 
+     * Parameters:
+     * evt - {Event} 
+     *
+     * Returns:
+     * {Boolean}
+     */
+    defaultClick: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        var notAfterDrag = !this.performedDrag;
+        this.performedDrag = false;
+        return notAfterDrag;
+    },
+
+    /**
+     * Method: defaultDblClick
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultDblClick: function (evt) {
+        var newCenter = this.map.getLonLatFromViewPortPx( evt.xy ); 
+        this.map.setCenter(newCenter, this.map.zoom + 1);
+        OpenLayers.Event.stop(evt);
+        return false;
+    },
+
+    /**
+     * Method: defaultMouseDown
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseDown: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        this.mouseDragStart = evt.xy.clone();
+        this.performedDrag  = false;
+        if (evt.shiftKey) {
+            this.map.div.style.cursor = "crosshair";
+            this.zoomBox = OpenLayers.Util.createDiv('zoomBox',
+                                                     this.mouseDragStart,
+                                                     null,
+                                                     null,
+                                                     "absolute",
+                                                     "2px solid red");
+            this.zoomBox.style.backgroundColor = "white";
+            this.zoomBox.style.filter = "alpha(opacity=50)"; // IE
+            this.zoomBox.style.opacity = "0.50";
+            this.zoomBox.style.fontSize = "1px";
+            this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+            this.map.viewPortDiv.appendChild(this.zoomBox);
+        }
+        document.onselectstart=function() { return false; };
+        OpenLayers.Event.stop(evt);
+    },
+
+    /**
+     * Method: defaultMouseMove
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseMove: function (evt) {
+        // record the mouse position, used in onWheelEvent
+        this.mousePosition = evt.xy.clone();
+
+        if (this.mouseDragStart != null) {
+            if (this.zoomBox) {
+                var deltaX = Math.abs(this.mouseDragStart.x - evt.xy.x);
+                var deltaY = Math.abs(this.mouseDragStart.y - evt.xy.y);
+                this.zoomBox.style.width = Math.max(1, deltaX) + "px";
+                this.zoomBox.style.height = Math.max(1, deltaY) + "px";
+                if (evt.xy.x < this.mouseDragStart.x) {
+                    this.zoomBox.style.left = evt.xy.x+"px";
+                }
+                if (evt.xy.y < this.mouseDragStart.y) {
+                    this.zoomBox.style.top = evt.xy.y+"px";
+                }
+            } else {
+                var deltaX = this.mouseDragStart.x - evt.xy.x;
+                var deltaY = this.mouseDragStart.y - evt.xy.y;
+                var size = this.map.getSize();
+                var newXY = new OpenLayers.Pixel(size.w / 2 + deltaX,
+                                                 size.h / 2 + deltaY);
+                var newCenter = this.map.getLonLatFromViewPortPx( newXY ); 
+                this.map.setCenter(newCenter, null, true);
+                this.mouseDragStart = evt.xy.clone();
+                this.map.div.style.cursor = "move";
+            }
+            this.performedDrag = true;
+        }
+    },
+
+    /**
+     * Method: defaultMouseUp
+     * 
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     */
+    defaultMouseUp: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        if (this.zoomBox) {
+            this.zoomBoxEnd(evt);    
+        } else {
+            if (this.performedDrag) {
+                this.map.setCenter(this.map.center);
+            }
+        }
+        document.onselectstart=null;
+        this.mouseDragStart = null;
+        this.map.div.style.cursor = "";
+    },
+
+    /**
+     * Method: defaultMouseOut
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseOut: function (evt) {
+        if (this.mouseDragStart != null && 
+            OpenLayers.Util.mouseLeft(evt, this.map.div)) {
+            if (this.zoomBox) {
+                this.removeZoomBox();
+            }
+            this.mouseDragStart = null;
+        }
+    },
+
+
+    /** 
+     * Method: defaultWheelUp
+     * User spun scroll wheel up
+     * 
+     */
+    defaultWheelUp: function(evt) {
+        if (this.map.getZoom() <= this.map.getNumZoomLevels()) {
+            this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),
+                               this.map.getZoom() + 1);
+        }
+    },
+
+    /**
+     * Method: defaultWheelDown
+     * User spun scroll wheel down
+     */
+    defaultWheelDown: function(evt) {
+        if (this.map.getZoom() > 0) {
+            this.map.setCenter(this.map.getLonLatFromPixel(evt.xy),
+                               this.map.getZoom() - 1);
+        }
+    },
+
+    /**
+     * Method: zoomBoxEnd
+     * Zoombox function. 
+     */
+    zoomBoxEnd: function(evt) {
+        if (this.mouseDragStart != null) {
+            if (Math.abs(this.mouseDragStart.x - evt.xy.x) > 5 ||    
+                Math.abs(this.mouseDragStart.y - evt.xy.y) > 5) {   
+                var start = this.map.getLonLatFromViewPortPx( this.mouseDragStart ); 
+                var end = this.map.getLonLatFromViewPortPx( evt.xy );
+                var top = Math.max(start.lat, end.lat);
+                var bottom = Math.min(start.lat, end.lat);
+                var left = Math.min(start.lon, end.lon);
+                var right = Math.max(start.lon, end.lon);
+                var bounds = new OpenLayers.Bounds(left, bottom, right, top);
+                this.map.zoomToExtent(bounds);
+            } else {
+                var end = this.map.getLonLatFromViewPortPx( evt.xy );
+                this.map.setCenter(new OpenLayers.LonLat(
+                  (end.lon),
+                  (end.lat)
+                 ), this.map.getZoom() + 1);
+            }    
+            this.removeZoomBox();
+       }
+    },
+
+    /**
+     * Method: removeZoomBox
+     * Remove the zoombox from the screen and nullify our reference to it.
+     */
+    removeZoomBox: function() {
+        this.map.viewPortDiv.removeChild(this.zoomBox);
+        this.zoomBox = null;
+    },
+
+
+/**
+ *  Mouse ScrollWheel code thanks to http://adomas.org/javascript-mouse-wheel/
+ */
+
+
+    /**
+     * Method: onWheelEvent
+     * Catch the wheel event and handle it xbrowserly
+     *
+     * Parameters: 
+     * e - {Event} 
+     */
+    onWheelEvent: function(e){
+    
+        // first determine whether or not the wheeling was inside the map
+        var inMap = false;
+        var elem = OpenLayers.Event.element(e);
+        while(elem != null) {
+            if (this.map && elem == this.map.div) {
+                inMap = true;
+                break;
+            }
+            elem = elem.parentNode;
+        }
+        
+        if (inMap) {
+            
+            var delta = 0;
+            if (!e) {
+                e = window.event;
+            }
+            if (e.wheelDelta) {
+                delta = e.wheelDelta/120; 
+                if (window.opera && window.opera.version() < 9.2) {
+                    delta = -delta;
+                }
+            } else if (e.detail) {
+                delta = -e.detail / 3;
+            }
+            if (delta) {
+                // add the mouse position to the event because mozilla has a bug
+                // with clientX and clientY (see https://bugzilla.mozilla.org/show_bug.cgi?id=352179)
+                // getLonLatFromViewPortPx(e) returns wrong values
+                e.xy = this.mousePosition;
+
+                if (delta < 0) {
+                   this.defaultWheelDown(e);
+                } else {
+                   this.defaultWheelUp(e);
+                }
+            }
+            
+            //only wheel the map, not the window
+            OpenLayers.Event.stop(e);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Control.MouseDefaults"
+});
+/* ======================================================================
     OpenLayers/Control/MousePosition.js
    ====================================================================== */
 
@@ -5630,7 +8726,7 @@
      * APIProperty: numDigits
      * {Integer}
      */
-    numdigits: 5,
+    numDigits: 5,
     
     /** 
      * APIProperty: granularity
@@ -5734,7 +8830,7 @@
      * lonLat - {<OpenLayers.LonLat>} Location to display
      */
     formatOutput: function(lonLat) {
-        var digits = parseInt(this.numdigits);
+        var digits = parseInt(this.numDigits);
         var newHtml =
             this.prefix +
             lonLat.lon.toFixed(digits) +
@@ -5755,6 +8851,408 @@
     CLASS_NAME: "OpenLayers.Control.MousePosition"
 });
 /* ======================================================================
+    OpenLayers/Control/NavigationHistory.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.NavigationHistory
+ * A navigation history control.  This is a meta-control, that creates two
+ *     dependent controls: <previous> and <next>.  Call the trigger method
+ *     on the <previous> and <next> controls to restore previous and next
+ *     history states.  The previous and next controls will become active
+ *     when there are available states to restore and will become deactive
+ *     when there are no states to restore.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.NavigationHistory = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * Property: type
+     * {String} Note that this control is not intended to be added directly
+     *     to a control panel.  Instead, add the sub-controls previous and
+     *     next.  These sub-controls are button type controls that activate
+     *     and deactivate themselves.  If this parent control is added to
+     *     a panel, it will act as a toggle.
+     */
+    type: OpenLayers.Control.TYPE_TOGGLE,
+
+    /**
+     * APIProperty: previous
+     * {<OpenLayers.Control>} A button type control whose trigger method restores
+     *     the previous state managed by this control.
+     */
+    previous: null,
+    
+    /**
+     * APIProperty: previousOptions
+     * {Object} Set this property on the options argument of the constructor
+     *     to set optional properties on the <previous> control.
+     */
+    previousOptions: null,
+    
+    /**
+     * APIProperty: next
+     * {<OpenLayers.Control>} A button type control whose trigger method restores
+     *     the next state managed by this control.
+     */
+    next: null,
+
+    /**
+     * APIProperty: nextOptions
+     * {Object} Set this property on the options argument of the constructor
+     *     to set optional properties on the <next> control.
+     */
+    nextOptions: null,
+
+    /**
+     * APIProperty: limit
+     * {Integer} Optional limit on the number of history items to retain.  If
+     *     null, there is no limit.  Default is 50.
+     */
+    limit: 50,
+
+    /**
+     * Property: activateOnDraw
+     * {Boolean} Activate the control when it is first added to the map.
+     *     Default is true.
+     */
+    activateOnDraw: true,
+
+    /**
+     * Property: clearOnDeactivate
+     * {Boolean} Clear the history when the control is deactivated.  Default
+     *     is false.
+     */
+    clearOnDeactivate: false,
+
+    /**
+     * Property: registry
+     * {Object} An object with keys corresponding to event types.  Values
+     *     are functions that return an object representing the current state.
+     */
+    registry: null,
+
+    /**
+     * Property: nextStack
+     * {Array} Array of items in the history.
+     */
+    nextStack: null,
+
+    /**
+     * Property: previousStack
+     * {Array} List of items in the history.  First item represents the current
+     *     state.
+     */
+    previousStack: null,
+    
+    /**
+     * Property: listeners
+     * {Object} An object containing properties corresponding to event types.
+     *     This object is used to configure the control and is modified on
+     *     construction.
+     */
+    listeners: null,
+    
+    /**
+     * Property: restoring
+     * {Boolean} Currently restoring a history state.  This is set to true
+     *     before calling restore and set to false after restore returns.
+     */
+    restoring: false,
+    
+    /**
+     * Constructor: OpenLayers.Control.NavigationHistory 
+     * 
+     * Parameters:
+     * options - {Object} An optional object whose properties will be used
+     *     to extend the control.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        
+        this.registry = OpenLayers.Util.extend({
+            "moveend": function() {
+                return {
+                    center: this.map.getCenter(),
+                    resolution: this.map.getResolution()                
+                };
+            }
+        }, this.registry);
+        
+        this.clear();
+
+        var previousOptions = {
+            trigger: OpenLayers.Function.bind(this.previousTrigger, this),
+            displayClass: this.displayClass + " " + this.displayClass + "Previous"
+        };
+        OpenLayers.Util.extend(previousOptions, this.previousOptions);
+        this.previous = new OpenLayers.Control.Button(previousOptions);
+        
+        var nextOptions = {
+            trigger: OpenLayers.Function.bind(this.nextTrigger, this),
+            displayClass: this.displayClass + " " + this.displayClass + "Next"
+        };
+        OpenLayers.Util.extend(nextOptions, this.nextOptions);
+        this.next = new OpenLayers.Control.Button(nextOptions);
+
+    },
+    
+    /**
+     * Method: onPreviousChange
+     * Called when the previous history stack changes.
+     *
+     * Parameters:
+     * state - {Object} An object representing the state to be restored
+     *     if previous is triggered again or null if no previous states remain.
+     * length - {Integer} The number of remaining previous states that can
+     *     be restored.
+     */
+    onPreviousChange: function(state, length) {
+        if(state && !this.previous.active) {
+            this.previous.activate();
+        } else if(!state && this.previous.active) {
+            this.previous.deactivate();
+        }
+    },
+    
+    /**
+     * Method: onNextChange
+     * Called when the next history stack changes.
+     *
+     * Parameters:
+     * state - {Object} An object representing the state to be restored
+     *     if next is triggered again or null if no next states remain.
+     * length - {Integer} The number of remaining next states that can
+     *     be restored.
+     */
+    onNextChange: function(state, length) {
+        if(state && !this.next.active) {
+            this.next.activate();
+        } else if(!state && this.next.active) {
+            this.next.deactivate();
+        }
+    },
+    
+    /**
+     * APIMethod: destroy
+     * Destroy the control.
+     */
+    destroy: function() {
+        OpenLayers.Control.prototype.destroy.apply(this);
+        this.previous.destroy();
+        this.next.destroy();
+        this.deactivate();
+        for(var prop in this) {
+            this[prop] = null;
+        }
+    },
+    
+    /** 
+     * Method: setMap
+     * Set the map property for the control and <previous> and <next> child
+     *     controls.
+     *
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        this.map = map;
+        this.next.setMap(map);
+        this.previous.setMap(map);
+    },
+
+    /**
+     * Method: draw
+     * Called when the control is added to the map.
+     */
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        this.next.draw();
+        this.previous.draw();
+        if(this.activateOnDraw) {
+            this.activate();
+        }
+    },
+    
+    /**
+     * Method: previousTrigger
+     * Restore the previous state.  If no items are in the previous history
+     *     stack, this has no effect.
+     *
+     * Returns:
+     * {Object} Item representing state that was restored.  Undefined if no
+     *     items are in the previous history stack.
+     */
+    previousTrigger: function() {
+        var current = this.previousStack.shift();
+        var state = this.previousStack.shift();
+        if(state != undefined) {
+            this.nextStack.unshift(current);
+            this.previousStack.unshift(state);
+            this.restoring = true;
+            this.restore(state);
+            this.restoring = false;
+            this.onNextChange(this.nextStack[0], this.nextStack.length);
+            this.onPreviousChange(
+                this.previousStack[1], this.previousStack.length - 1
+            );
+        } else {
+            this.previousStack.unshift(current);
+        }
+        return state;
+    },
+    
+    /**
+     * APIMethod: nextTrigger
+     * Restore the next state.  If no items are in the next history
+     *     stack, this has no effect.  The next history stack is populated
+     *     as states are restored from the previous history stack.
+     *
+     * Returns:
+     * {Object} Item representing state that was restored.  Undefined if no
+     *     items are in the next history stack.
+     */
+    nextTrigger: function() {
+        var state = this.nextStack.shift();
+        if(state != undefined) {
+            this.previousStack.unshift(state);
+            this.restoring = true;
+            this.restore(state);
+            this.restoring = false;
+            this.onNextChange(this.nextStack[0], this.nextStack.length);
+            this.onPreviousChange(
+                this.previousStack[1], this.previousStack.length - 1
+            );
+        }
+        return state;
+    },
+    
+    /**
+     * APIMethod: clear
+     * Clear history.
+     */
+    clear: function() {
+        this.previousStack = [];
+        this.nextStack = [];
+    },
+
+    /**
+     * Method: restore
+     * Update the state with the given object.
+     *
+     * Parameters:
+     * state - {Object} An object representing the state to restore.
+     */
+    restore: function(state) {
+        var zoom = this.map.getZoomForResolution(state.resolution);
+        this.map.setCenter(state.center, zoom);
+    },
+    
+    /**
+     * Method: setListeners
+     * Sets functions to be registered in the listeners object.
+     */
+    setListeners: function() {
+        this.listeners = {};
+        for(var type in this.registry) {
+            this.listeners[type] = OpenLayers.Function.bind(function() {
+                if(!this.restoring) {
+                    var state = this.registry[type].apply(this, arguments);
+                    this.previousStack.unshift(state);
+                    if(this.previousStack.length > 1) {
+                        this.onPreviousChange(
+                            this.previousStack[1], this.previousStack.length - 1
+                        );
+                    }
+                    if(this.previousStack.length > (this.limit + 1)) {
+                        this.previousStack.pop();
+                    }
+                    if(this.nextStack.length > 0) {
+                        this.nextStack = [];
+                        this.onNextChange(null, 0);
+                    }
+                }
+                return true;
+            }, this);
+        }
+    },
+
+    /**
+     * APIMethod: activate
+     * Activate the control.  This registers any listeners.
+     *
+     * Returns:
+     * {Boolean} Control successfully activated.
+     */
+    activate: function() {
+        var activated = false;
+        if(this.map) {
+            if(OpenLayers.Control.prototype.activate.apply(this)) {
+                if(this.listeners == null) {
+                    this.setListeners();
+                }
+                for(var type in this.listeners) {
+                    this.map.events.register(type, this, this.listeners[type]);
+                }
+                activated = true;
+                if(this.previousStack.length == 0) {
+                    this.initStack();
+                }
+            }
+        }
+        return activated;
+    },
+    
+    /**
+     * Method: initStack
+     * Called after the control is activated if the previous history stack is
+     *     empty.
+     */
+    initStack: function() {
+        if(this.map.getCenter()) {
+            this.listeners.moveend();
+        }
+    },
+    
+    /**
+     * APIMethod: deactivate
+     * Deactivate the control.  This unregisters any listeners.
+     *
+     * Returns:
+     * {Boolean} Control successfully deactivated.
+     */
+    deactivate: function() {
+        var deactivated = false;
+        if(this.map) {
+            if(OpenLayers.Control.prototype.deactivate.apply(this)) {
+                for(var type in this.listeners) {
+                    this.map.events.unregister(
+                        type, this, this.listeners[type]
+                    );
+                }
+                if(this.clearOnDeactivate) {
+                    this.clear();
+                }
+                deactivated = true;
+            }
+        }
+        return deactivated;
+    },
+    
+    CLASS_NAME: "OpenLayers.Control.NavigationHistory"
+});
+
+/* ======================================================================
     OpenLayers/Control/PanZoom.js
    ====================================================================== */
 
@@ -5871,7 +9369,7 @@
     _addButton:function(id, img, xy, sz) {
         var imgLocation = OpenLayers.Util.getImagesLocation() + img;
         var btn = OpenLayers.Util.createAlphaImageDiv(
-                                    "OpenLayers_Control_PanZoom_" + id, 
+                                    this.id + "_" + id, 
                                     xy, sz, imgLocation, "absolute");
 
         //we want to add the outer div
@@ -5959,6 +9457,373 @@
  */
 OpenLayers.Control.PanZoom.Y = 4;
 /* ======================================================================
+    OpenLayers/Control/Panel.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Panel
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.Panel = OpenLayers.Class(OpenLayers.Control, {
+    /**
+     * Property: controls
+     * {Array(<OpenLayers.Control>)}
+     */
+    controls: null,    
+    
+    /** 
+     * APIProperty: defaultControl
+     * <OpenLayers.Control> The control which is activated when the control is
+     * activated (turned on), which also happens at instantiation.
+     */
+    defaultControl: null, 
+
+    /**
+     * Constructor: OpenLayers.Control.Panel
+     * Create a new control panel.
+     * 
+     * Parameters:
+     * options - {Object} An optional object whose properties will be used
+     *     to extend the control.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.controls = [];
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        OpenLayers.Control.prototype.destroy.apply(this, arguments);
+        for(var i = this.controls.length - 1 ; i >= 0; i--) {
+            if(this.controls[i].events) {
+                this.controls[i].events.un({
+                    "activate": this.redraw,
+                    "deactivate": this.redraw,
+                    scope: this
+                });
+            }
+            OpenLayers.Event.stopObservingElement(this.controls[i].panel_div);
+            this.controls[i].panel_div = null;
+        }    
+    },
+
+    /**
+     * APIMethod: activate
+     */
+    activate: function() {
+        if (OpenLayers.Control.prototype.activate.apply(this, arguments)) {
+            for(var i=0, len=this.controls.length; i<len; i++) {
+                if (this.controls[i] == this.defaultControl) {
+                    this.controls[i].activate();
+                }
+            }    
+            this.redraw();
+            return true;
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * APIMethod: deactivate
+     */
+    deactivate: function() {
+        if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+            for(var i=0, len=this.controls.length; i<len; i++) {
+                this.controls[i].deactivate();
+            }    
+            return true;
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: draw
+     *
+     * Returns:
+     * {DOMElement}
+     */    
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        for (var i=0, len=this.controls.length; i<len; i++) {
+            this.map.addControl(this.controls[i]);
+            this.controls[i].deactivate();
+            this.controls[i].events.on({
+                "activate": this.redraw,
+                "deactivate": this.redraw,
+                scope: this
+            });
+        }
+        this.activate();
+        return this.div;
+    },
+
+    /**
+     * Method: redraw
+     */
+    redraw: function() {
+        this.div.innerHTML = "";
+        if (this.active) {
+            for (var i=0, len=this.controls.length; i<len; i++) {
+                var element = this.controls[i].panel_div;
+                if (this.controls[i].active) {
+                    element.className = this.controls[i].displayClass + "ItemActive";
+                } else {    
+                    element.className = this.controls[i].displayClass + "ItemInactive";
+                }    
+                this.div.appendChild(element);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: activateControl
+     * 
+     * Parameters:
+     * control - {<OpenLayers.Control>}
+     */
+    activateControl: function (control) {
+        if (!this.active) { return false; }
+        if (control.type == OpenLayers.Control.TYPE_BUTTON) {
+            control.trigger();
+            this.redraw();
+            return;
+        }
+        if (control.type == OpenLayers.Control.TYPE_TOGGLE) {
+            if (control.active) {
+                control.deactivate();
+            } else {
+                control.activate();
+            }
+            this.redraw();
+            return;
+        }
+        for (var i=0, len=this.controls.length; i<len; i++) {
+            if (this.controls[i] != control) {
+                if (this.controls[i].type != OpenLayers.Control.TYPE_TOGGLE) {
+                    this.controls[i].deactivate();
+                }
+            }
+        }
+        control.activate();
+    },
+
+    /**
+     * APIMethod: addControls
+     * To build a toolbar, you add a set of controls to it. addControls
+     * lets you add a single control or a list of controls to the 
+     * Control Panel.
+     *
+     * Parameters:
+     * controls - {<OpenLayers.Control>} 
+     */    
+    addControls: function(controls) {
+        if (!(controls instanceof Array)) {
+            controls = [controls];
+        }
+        this.controls = this.controls.concat(controls);
+        
+        // Give each control a panel_div which will be used later.
+        // Access to this div is via the panel_div attribute of the 
+        // control added to the panel.
+        // Also, stop mousedowns and clicks, but don't stop mouseup,
+        // since they need to pass through.
+        for (var i=0, len=controls.length; i<len; i++) {
+            var element = document.createElement("div");
+            var textNode = document.createTextNode(" ");
+            controls[i].panel_div = element;
+            if (controls[i].title != "") {
+                controls[i].panel_div.title = controls[i].title;
+            }
+            OpenLayers.Event.observe(controls[i].panel_div, "click", 
+                OpenLayers.Function.bind(this.onClick, this, controls[i]));
+            OpenLayers.Event.observe(controls[i].panel_div, "mousedown", 
+                OpenLayers.Function.bindAsEventListener(OpenLayers.Event.stop));
+        }    
+
+        if (this.map) { // map.addControl() has already been called on the panel
+            for (var i=0, len=controls.length; i<len; i++) {
+                this.map.addControl(controls[i]);
+                controls[i].deactivate();
+                controls[i].events.on({
+                    "activate": this.redraw,
+                    "deactivate": this.redraw,
+                    scope: this
+                });
+            }
+            this.redraw();
+        }
+    },
+   
+    /**
+     * Method: onClick
+     */
+    onClick: function (ctrl, evt) {
+        OpenLayers.Event.stop(evt ? evt : window.event);
+        this.activateControl(ctrl);
+    },
+
+    /**
+     * APIMethod: getControlsBy
+     * Get a list of controls with properties matching the given criteria.
+     *
+     * Parameter:
+     * 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(control[property]) evaluates to true, the control will be
+     *     included in the array returned.  If no controls 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) {
+        var test = (typeof match.test == "function");
+        var found = OpenLayers.Array.filter(this.controls, function(item) {
+            return item[property] == match || (test && match.test(item[property]));
+        });
+        return found;
+    },
+
+    /**
+     * APIMethod: getControlsByName
+     * Get a list of contorls with names matching the given name.
+     *
+     * Parameter:
+     * match - {String | Object} A control 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(control.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 name.
+     *     An empty array is returned if no matches are found.
+     */
+    getControlsByName: function(match) {
+        return this.getControlsBy("name", match);
+    },
+
+    /**
+     * APIMethod: getControlsByClass
+     * Get a list of controls of a given type (CLASS_NAME).
+     *
+     * Parameter:
+     * match - {String | Object} A control class name.  The type 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 type.
+     *     An empty array is returned if no matches are found.
+     */
+    getControlsByClass: function(match) {
+        return this.getControlsBy("CLASS_NAME", match);
+    },
+
+    CLASS_NAME: "OpenLayers.Control.Panel"
+});
+
+/* ======================================================================
+    OpenLayers/Control/Scale.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Scale
+ * Display a small scale indicator on the map.
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.Scale = OpenLayers.Class(OpenLayers.Control, {
+    
+    /**
+     * Parameter: element
+     * {DOMElement}
+     */
+    element: null,
+    
+    /**
+     * Constructor: OpenLayers.Control.Scale
+     * 
+     * Parameters:
+     * element - {DOMElement} 
+     * options - {Object} 
+     */
+    initialize: function(element, options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.element = OpenLayers.Util.getElement(element);        
+    },
+
+    /**
+     * Method: draw
+     * 
+     * Returns:
+     * {DOMElement}
+     */    
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        if (!this.element) {
+            this.element = document.createElement("div");
+            this.div.appendChild(this.element);
+        }
+        this.map.events.register( 'moveend', this, this.updateScale);
+        this.updateScale();
+        return this.div;
+    },
+   
+    /**
+     * Method: updateScale
+     */
+    updateScale: function() {
+        var scale = this.map.getScale();
+        if (!scale) {
+            return;
+        }
+
+        if (scale >= 9500 && scale <= 950000) {
+            scale = Math.round(scale / 1000) + "K";
+        } else if (scale >= 950000) {
+            scale = Math.round(scale / 1000000) + "M";
+        } else {
+            scale = Math.round(scale);
+        }    
+        
+        this.element.innerHTML = OpenLayers.i18n("scale", {'scaleDenom':scale});
+    }, 
+
+    CLASS_NAME: "OpenLayers.Control.Scale"
+});
+
+/* ======================================================================
     OpenLayers/Control/ScaleLine.js
    ====================================================================== */
 
@@ -6117,7 +9982,7 @@
             return;
         }
 
-        var curMapUnits = this.map.units;
+        var curMapUnits = this.map.getUnits();
         var inches = OpenLayers.INCHES_PER_UNIT;
 
         // convert maxWidth to map units
@@ -6163,6 +10028,45 @@
 });
 
 /* ======================================================================
+    OpenLayers/Control/ZoomToMaxExtent.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ZoomToMaxExtent 
+ * Imlements a very simple button control. Designed to be used with a 
+ * <OpenLayers.Control.Panel>.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.ZoomToMaxExtent = OpenLayers.Class(OpenLayers.Control, {
+    /**
+     * Property: type
+     * TYPE_BUTTON.
+     */
+    type: OpenLayers.Control.TYPE_BUTTON,
+    
+    /*
+     * Method: trigger
+     * Do the zoom.
+     */
+    trigger: function() {
+        if (this.map) {
+            this.map.zoomToMaxExtent();
+        }    
+    },
+
+    CLASS_NAME: "OpenLayers.Control.ZoomToMaxExtent"
+});
+/* ======================================================================
     OpenLayers/Events.js
    ====================================================================== */
 
@@ -6273,6 +10177,21 @@
     },
 
     /**
+     * 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. 
      *
@@ -6520,7 +10439,7 @@
     BROWSER_EVENTS: [
         "mouseover", "mouseout",
         "mousedown", "mouseup", "mousemove", 
-        "click", "dblclick",
+        "click", "dblclick", "rightclick", "dblrightclick",
         "resize", "focus", "blur"
     ],
 
@@ -6560,6 +10479,34 @@
      */
     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,      
+
     /**
      * Constructor: OpenLayers.Events
      * Construct an OpenLayers.Events object.
@@ -6570,11 +10517,12 @@
      * eventTypes - {Array(String)} Array of custom application events 
      * 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) {
+    initialize: function (object, element, eventTypes, fallThrough, options) {
+        OpenLayers.Util.extend(this, options);
         this.object     = object;
         this.element    = element;
-        this.eventTypes = eventTypes;
         this.fallThrough = fallThrough;
         this.listeners  = {};
 
@@ -6586,9 +10534,10 @@
 
         // if eventTypes is specified, create a listeners list for each 
         // custom application event.
-        if (this.eventTypes != null) {
-            for (var i = 0; i < this.eventTypes.length; i++) {
-                this.addEventType(this.eventTypes[i]);
+        this.eventTypes = [];
+        if (eventTypes != null) {
+            for (var i=0, len=eventTypes.length; i<len; i++) {
+                this.addEventType(eventTypes[i]);
             }
         }
         
@@ -6625,6 +10574,7 @@
      */
     addEventType: function(eventName) {
         if (!this.listeners[eventName]) {
+            this.eventTypes.push(eventName);
             this.listeners[eventName] = [];
         }
     },
@@ -6636,7 +10586,7 @@
      * element - {HTMLDOMElement} a DOM element to attach browser events to
      */
     attachToElement: function (element) {
-        for (var i = 0; i < this.BROWSER_EVENTS.length; i++) {
+        for (var i=0, len=this.BROWSER_EVENTS.length; i<len; i++) {
             var eventType = this.BROWSER_EVENTS[i];
 
             // every browser event has a corresponding application event 
@@ -6700,16 +10650,14 @@
      */
     register: function (type, obj, func) {
 
-        if (func != null && 
-            ((this.eventTypes && OpenLayers.Util.indexOf(this.eventTypes, type) != -1) ||
-            OpenLayers.Util.indexOf(this.BROWSER_EVENTS, type) != -1)) {
+        if ( (func != null) && 
+             (OpenLayers.Util.indexOf(this.eventTypes, type) != -1) ) {
+
             if (obj == null)  {
                 obj = this.object;
             }
             var listeners = this.listeners[type];
-            if (listeners != null) {
-                listeners.push( {obj: obj, func: func} );
-            }
+            listeners.push( {obj: obj, func: func} );
         }
     },
 
@@ -6778,7 +10726,7 @@
         }
         var listeners = this.listeners[type];
         if (listeners != null) {
-            for (var i = 0; i < listeners.length; i++) {
+            for (var i=0, len=listeners.length; i<len; i++) {
                 if (listeners[i].obj == obj && listeners[i].func == func) {
                     listeners.splice(i, 1);
                     break;
@@ -6832,7 +10780,7 @@
                             this.listeners[type].slice() : null;
         if ((listeners != null) && (listeners.length > 0)) {
             var continueChain;
-            for (var i = 0; i < listeners.length; i++) {
+            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]);
@@ -6860,11 +10808,25 @@
      * evt - {Event} 
      */
     handleBrowserEvent: function (evt) {
-        evt.xy = this.getMousePosition(evt); 
+        if (this.includeXY) {
+            evt.xy = this.getMousePosition(evt);
+        } 
         this.triggerEvent(evt.type, evt);
     },
 
     /**
+     * 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:
@@ -6875,26 +10837,137 @@
      *                      for offsets
      */
     getMousePosition: function (evt) {
-        if (!this.element.offsets) {
-            this.element.offsets = OpenLayers.Util.pagePosition(this.element);
-            this.element.offsets[0] += (document.documentElement.scrollLeft
+        if (!this.includeXY) {
+            this.clearMouseCache();
+        } else if (!this.element.hasScrollEvent) {
+            OpenLayers.Event.observe(window, 'scroll', 
+                OpenLayers.Function.bind(this.clearMouseCache, this)); 
+            this.element.hasScrollEvent = true;
+        }
+        
+        if (!this.element.scrolls) {
+            this.element.scrolls = [];
+            this.element.scrolls[0] = (document.documentElement.scrollLeft
                          || document.body.scrollLeft);
-            this.element.offsets[1] += (document.documentElement.scrollTop
+            this.element.scrolls[1] = (document.documentElement.scrollTop
                          || document.body.scrollTop);
         }
+
+        if (!this.element.lefttop) {
+            this.element.lefttop = [];
+            this.element.lefttop[0] = (document.documentElement.clientLeft || 0);
+            this.element.lefttop[1] = (document.documentElement.clientTop || 0);
+        }
+        
+        if (!this.element.offsets) {
+            this.element.offsets = OpenLayers.Util.pagePosition(this.element);
+            this.element.offsets[0] += this.element.scrolls[0];
+            this.element.offsets[1] += this.element.scrolls[1];
+        }
         return new OpenLayers.Pixel(
-            (evt.clientX + (document.documentElement.scrollLeft
-                         || document.body.scrollLeft)) - this.element.offsets[0]
-                         - (document.documentElement.clientLeft || 0), 
-            (evt.clientY + (document.documentElement.scrollTop
-                         || document.body.scrollTop)) - this.element.offsets[1]
-                         - (document.documentElement.clientTop || 0)
+            (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]
         ); 
     },
 
     CLASS_NAME: "OpenLayers.Events"
 });
 /* ======================================================================
+    OpenLayers/Format.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Format
+ * Base class for format reading/writing a variety of formats.  Subclasses
+ *     of OpenLayers.Format are expected to have read and write methods.
+ */
+OpenLayers.Format = OpenLayers.Class({
+    
+    /**
+     * APIProperty: externalProjection
+     * {<OpenLayers.Projection>} When passed a externalProjection and
+     *     internalProjection, the format will reproject the geometries it
+     *     reads or writes. The externalProjection is the projection used by
+     *     the content which is passed into read or which comes out of write.
+     *     In order to reproject, a projection transformation function for the
+     *     specified projections must be available. This support may be 
+     *     provided via proj4js or via a custom transformation function. See
+     *     {<OpenLayers.Projection.addTransform>} for more information on
+     *     custom transformations.
+     */
+    externalProjection: null,
+
+    /**
+     * APIProperty: internalProjection
+     * {<OpenLayers.Projection>} When passed a externalProjection and
+     *     internalProjection, the format will reproject the geometries it
+     *     reads or writes. The internalProjection is the projection used by
+     *     the geometries which are returned by read or which are passed into
+     *     write.  In order to reproject, a projection transformation function
+     *     for the specified projections must be available. This support may be
+     *     provided via proj4js or via a custom transformation function. See
+     *     {<OpenLayers.Projection.addTransform>} for more information on
+     *     custom transformations.
+     */
+    internalProjection: null,
+
+    /**
+     * Constructor: OpenLayers.Format
+     * Instances of this class are not useful.  See one of the subclasses.
+     *
+     * Parameters:
+     * options - {Object} An optional object with properties to set on the
+     *           format
+     *
+     * Returns:
+     * An instance of OpenLayers.Format
+     */
+    initialize: function(options) {
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /**
+     * Method: read
+     * Read data from a string, and return an object whose type depends on the
+     * subclass. 
+     * 
+     * Parameters:
+     * data - {string} Data to read/parse.
+     *
+     * Returns:
+     * Depends on the subclass
+     */
+    read: function(data) {
+        OpenLayers.Console.userError(OpenLayers.i18n("readNotImplemented"));
+    },
+    
+    /**
+     * Method: write
+     * Accept an object, and return a string. 
+     *
+     * Parameters:
+     * object - {Object} Object to be serialized
+     *
+     * Returns:
+     * {String} A string representation of the object.
+     */
+    write: function(object) {
+        OpenLayers.Console.userError(OpenLayers.i18n("writeNotImplemented"));
+    },
+
+    CLASS_NAME: "OpenLayers.Format"
+});     
+/* ======================================================================
     OpenLayers/Lang/en.js
    ====================================================================== */
 
@@ -6975,7 +11048,7 @@
         "To get rid of this message, select a new BaseLayer " +
         "in the layer switcher in the upper-right corner.<br><br>" +
         "Most likely, this is because the ${layerLib} library " +
-        "script was either not correctly included.<br><br>" +
+        "script was not correctly included.<br><br>" +
         "Developers: For help getting this working correctly, " +
         "<a href='http://trac.openlayers.org/wiki/${layerLib}' " +
         "target='_blank'>click here</a>",
@@ -7019,6 +11092,193 @@
     'end': ''
 };
 /* ======================================================================
+    OpenLayers/Popup/Anchored.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Popup.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.Anchored
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Popup>
+ */
+OpenLayers.Popup.Anchored = 
+  OpenLayers.Class(OpenLayers.Popup, {
+
+    /** 
+     * Parameter: relativePosition
+     * {String} Relative position of the popup ("br", "tr", "tl" or "bl").
+     */
+    relativePosition: null,
+
+    /**
+     * Parameter: anchor
+     * {Object} Object to which we'll anchor the popup. Must expose a 
+     *     'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>).
+     */
+    anchor: null,
+
+    /** 
+    * Constructor: OpenLayers.Popup.Anchored
+    * 
+    * Parameters:
+    * id - {String}
+    * lonlat - {<OpenLayers.LonLat>}
+    * contentSize - {<OpenLayers.Size>}
+    * contentHTML - {String}
+    * anchor - {Object} Object which must expose a 'size' <OpenLayers.Size> 
+    *     and 'offset' <OpenLayers.Pixel> (generally an <OpenLayers.Icon>).
+    * closeBox - {Boolean}
+    * closeBoxCallback - {Function} Function to be called on closeBox click.
+    */
+    initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+                        closeBoxCallback) {
+        var newArguments = [
+            id, lonlat, contentSize, contentHTML, closeBox, closeBoxCallback
+        ];
+        OpenLayers.Popup.prototype.initialize.apply(this, newArguments);
+
+        this.anchor = (anchor != null) ? anchor 
+                                       : { size: new OpenLayers.Size(0,0),
+                                           offset: new OpenLayers.Pixel(0,0)};
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        this.anchor = null;
+        this.relativePosition = null;
+        
+        OpenLayers.Popup.prototype.destroy.apply(this, arguments);        
+    },
+
+    /**
+     * APIMethod: show
+     * Overridden from Popup since user might hide popup and then show() it 
+     *     in a new location (meaning we might want to update the relative
+     *     position on the show)
+     */
+    show: function() {
+        this.updatePosition();
+        OpenLayers.Popup.prototype.show.apply(this, arguments);
+    },
+
+    /**
+     * Method: moveTo
+     * Since the popup is moving to a new px, it might need also to be moved
+     *     relative to where the marker is. We first calculate the new 
+     *     relativePosition, and then we calculate the new px where we will 
+     *     put the popup, based on the new relative position. 
+     * 
+     *     If the relativePosition has changed, we must also call 
+     *     updateRelativePosition() to make any visual changes to the popup 
+     *     which are associated with putting it in a new relativePosition.
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     */
+    moveTo: function(px) {
+        var oldRelativePosition = this.relativePosition;
+        this.relativePosition = this.calculateRelativePosition(px);
+        
+        var newPx = this.calculateNewPx(px);
+        
+        var newArguments = new Array(newPx);        
+        OpenLayers.Popup.prototype.moveTo.apply(this, newArguments);
+        
+        //if this move has caused the popup to change its relative position, 
+        // we need to make the appropriate cosmetic changes.
+        if (this.relativePosition != oldRelativePosition) {
+            this.updateRelativePosition();
+        }
+    },
+
+    /**
+     * APIMethod: setSize
+     * 
+     * Parameters:
+     * contentSize - {<OpenLayers.Size>} the new size for the popup's 
+     *     contents div (in pixels).
+     */
+    setSize:function(contentSize) { 
+        OpenLayers.Popup.prototype.setSize.apply(this, arguments);
+
+        if ((this.lonlat) && (this.map)) {
+            var px = this.map.getLayerPxFromLonLat(this.lonlat);
+            this.moveTo(px);
+        }
+    },  
+    
+    /** 
+     * Method: calculateRelativePosition
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {String} The relative position ("br" "tr" "tl" "bl") at which the popup
+     *     should be placed.
+     */
+    calculateRelativePosition:function(px) {
+        var lonlat = this.map.getLonLatFromLayerPx(px);        
+        
+        var extent = this.map.getExtent();
+        var quadrant = extent.determineQuadrant(lonlat);
+        
+        return OpenLayers.Bounds.oppositeQuadrant(quadrant);
+    }, 
+
+    /**
+     * Method: updateRelativePosition
+     * The popup has been moved to a new relative location, so we may want to 
+     *     make some cosmetic adjustments to it. 
+     * 
+     *     Note that in the classic Anchored popup, there is nothing to do 
+     *     here, since the popup looks exactly the same in all four positions.
+     *     Subclasses such as the AnchoredBubble and Framed, however, will 
+     *     want to do something special here.
+     */
+    updateRelativePosition: function() {
+        //to be overridden by subclasses
+    },
+
+    /** 
+     * Method: calculateNewPx
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
+     *     relative to the passed-in px.
+     */
+    calculateNewPx:function(px) {
+        var newPx = px.offset(this.anchor.offset);
+        
+        //use contentSize if size is not already set
+        var size = this.size || this.contentSize;
+
+        var top = (this.relativePosition.charAt(0) == 't');
+        newPx.y += (top) ? -size.h : this.anchor.size.h;
+        
+        var left = (this.relativePosition.charAt(1) == 'l');
+        newPx.x += (left) ? -size.w : this.anchor.size.w;
+
+        return newPx;   
+    },
+
+    CLASS_NAME: "OpenLayers.Popup.Anchored"
+});
+/* ======================================================================
     OpenLayers/Projection.js
    ====================================================================== */
 
@@ -7198,6 +11458,1740 @@
     return point;
 };
 /* ======================================================================
+    OpenLayers/Renderer/Canvas.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.Canvas 
+ * A renderer based on the 2D 'canvas' drawing element.element
+ * 
+ * Inherits:
+ *  - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Canvas = OpenLayers.Class(OpenLayers.Renderer, {
+
+    /**
+     * Property: root
+     * {DOMElement} root element of canvas.
+     */
+    root: null,
+
+    /**
+     * Property: canvas
+     * {Canvas} The canvas context object.
+     */
+    canvas: null, 
+    
+    /**
+     * Property: features
+     * {Object} Internal object of feature/style pairs for use in redrawing the layer.
+     */
+    features: null, 
+   
+    /**
+     * Property: geometryMap
+     * {Object} Geometry -> Feature lookup table. Used by eraseGeometry to
+     *     lookup features to remove from our internal table (this.features)
+     *     when erasing geoms.
+     */
+    geometryMap: null,
+ 
+    /**
+     * Constructor: OpenLayers.Renderer.Canvas
+     *
+     * Parameters:
+     * containerID - {<String>} 
+     */
+    initialize: function(containerID) {
+        OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+        this.root = document.createElement("canvas");
+        this.container.appendChild(this.root);
+        this.canvas = this.root.getContext("2d");
+        this.features = {};
+        this.geometryMap = {};
+    },
+    
+    /** 
+     * Method: eraseGeometry
+     * Erase a geometry from the renderer. Because the Canvas renderer has
+     *     'memory' of the features that it has drawn, we have to remove the
+     *     feature so it doesn't redraw.   
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     */
+    eraseGeometry: function(geometry) {
+        this.eraseFeatures(this.features[this.geometryMap[geometry.id]][0]);
+    },
+
+    /**
+     * APIMethod: supported
+     * 
+     * Returns:
+     * {Boolean} Whether or not the browser supports the renderer class
+     */
+    supported: function() {
+        var canvas = document.createElement("canvas");
+        return !!canvas.getContext;
+    },    
+    
+    /**
+     * Method: setExtent
+     * Set the visible part of the layer.
+     *
+     * Resolution has probably changed, so we nullify the resolution 
+     * cache (this.resolution), then redraw. 
+     *
+     * Parameters:
+     * extent - {<OpenLayers.Bounds>} 
+     */
+    setExtent: function(extent) {
+        this.extent = extent.clone();
+        this.resolution = null;
+        this.redraw();
+    },
+    
+    /**
+     * Method: setSize
+     * Sets the size of the drawing surface.
+     *
+     * Once the size is updated, redraw the canvas.
+     *
+     * Parameters:
+     * size - {<OpenLayers.Size>} 
+     */
+    setSize: function(size) {
+        this.size = size.clone();
+        this.root.style.width = size.w + "px";
+        this.root.style.height = size.h + "px";
+        this.root.width = size.w;
+        this.root.height = size.h;
+        this.resolution = null;
+    },
+    
+    /**
+     * Method: drawFeature
+     * Draw the feature. Stores the feature in the features list,
+     * then redraws the layer. 
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     * style - {<Object>} 
+     */
+    drawFeature: function(feature, style) {
+        if(style == null) {
+            style = feature.style;
+        }
+        style = OpenLayers.Util.extend({
+          'fillColor': '#000000',
+          'strokeColor': '#000000',
+          'strokeWidth': 2,
+          'fillOpacity': 1,
+          'strokeOpacity': 1
+        }, style);  
+        this.features[feature.id] = [feature, style]; 
+        this.geometryMap[feature.geometry.id] = feature.id; 
+        this.redraw();
+    },
+
+
+    /** 
+     * Method: drawGeometry
+     * Used when looping (in redraw) over the features; draws
+     * the canvas. 
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} 
+     * style - {Object} 
+     * featureId - {<String>} 
+     */
+    drawGeometry: function(geometry, style) {
+        var className = geometry.CLASS_NAME;
+        if ((className == "OpenLayers.Geometry.Collection") ||
+            (className == "OpenLayers.Geometry.MultiPoint") ||
+            (className == "OpenLayers.Geometry.MultiLineString") ||
+            (className == "OpenLayers.Geometry.MultiPolygon")) {
+            for (var i = 0; i < geometry.components.length; i++) {
+                this.drawGeometry(geometry.components[i], style);
+            }
+            return;
+        };
+        switch (geometry.CLASS_NAME) {
+            case "OpenLayers.Geometry.Point":
+                this.drawPoint(geometry, style);
+                break;
+            case "OpenLayers.Geometry.LineString":
+                this.drawLineString(geometry, style);
+                break;
+            case "OpenLayers.Geometry.LinearRing":
+                this.drawLinearRing(geometry, style);
+                break;
+            case "OpenLayers.Geometry.Polygon":
+                this.drawPolygon(geometry, style);
+                break;
+            default:
+                break;
+        }
+    },
+
+    /**
+     * Method: drawExternalGraphic
+     * Called to draw External graphics. 
+     * 
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>}
+     * style    - {Object}
+     */ 
+    drawExternalGraphic: function(pt, style) {
+       var img = new Image();
+       img.src = style.externalGraphic;
+
+       var width = style.graphicWidth || style.graphicHeight;
+       var height = style.graphicHeight || style.graphicWidth;
+       width = width ? width : style.pointRadius*2;
+       height = height ? height : style.pointRadius*2;
+       var xOffset = (style.graphicXOffset != undefined) ?
+           style.graphicXOffset : -(0.5 * width);
+       var yOffset = (style.graphicYOffset != undefined) ?
+           style.graphicYOffset : -(0.5 * height);
+       var opacity = style.graphicOpacity || style.fillOpacity;
+       
+       var context = { img: img, 
+                       x: (pt[0]+xOffset), 
+                       y: (pt[1]+yOffset), 
+                       width: width, 
+                       height: height, 
+                       canvas: this.canvas };
+
+       img.onload = OpenLayers.Function.bind( function() {
+           this.canvas.drawImage(this.img, this.x, 
+                                 this.y, this.width, this.height);
+       }, context);   
+    },
+
+    /**
+     * Method: setCanvasStyle
+     * Prepare the canvas for drawing by setting various global settings.
+     *
+     * Parameters:
+     * type - {String} one of 'stroke', 'fill', or 'reset'
+     * style - {Object} Symbolizer hash
+     */
+    setCanvasStyle: function(type, style) {
+        if (type == "fill") {     
+            this.canvas.globalAlpha = style['fillOpacity'];
+            this.canvas.fillStyle = style['fillColor'];
+        } else if (type == "stroke") {  
+            this.canvas.globalAlpha = style['strokeOpacity'];
+            this.canvas.strokeStyle = style['strokeColor'];
+            this.canvas.lineWidth = style['strokeWidth'];
+        } else {
+            this.canvas.globalAlpha = 0;
+            this.canvas.lineWidth = 1;
+        }
+    },
+
+    /**
+     * Method: drawPoint
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>}
+     * style    - {Object}
+     */ 
+    drawPoint: function(geometry, style) {
+        var pt = this.getLocalXY(geometry);
+        
+        if (style.externalGraphic) {
+            this.drawExternalGraphic(pt, style);
+        } else {
+            this.setCanvasStyle("fill", style);
+            this.canvas.beginPath();
+            this.canvas.arc(pt[0], pt[1], 6, 0, Math.PI*2, true);
+            this.canvas.fill();
+            
+            this.setCanvasStyle("stroke", style);
+            this.canvas.beginPath();
+            this.canvas.arc(pt[0], pt[1], 6, 0, Math.PI*2, true);
+            this.canvas.stroke();
+            this.setCanvasStyle("reset");
+        }
+    },
+
+    /**
+     * Method: drawLineString
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>}
+     * style    - {Object}
+     */ 
+    drawLineString: function(geometry, style) {
+        this.setCanvasStyle("stroke", style);
+        this.canvas.beginPath();
+        var start = this.getLocalXY(geometry.components[0]);
+        this.canvas.moveTo(start[0], start[1]);
+        for(var i = 1; i < geometry.components.length; i++) {
+            var pt = this.getLocalXY(geometry.components[i]);
+            this.canvas.lineTo(pt[0], pt[1]);
+        }
+        this.canvas.stroke();
+        this.setCanvasStyle("reset");
+    },    
+    
+    /**
+     * Method: drawLinearRing
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>}
+     * style    - {Object}
+     */ 
+    drawLinearRing: function(geometry, style) {
+        this.setCanvasStyle("fill", style);
+        this.canvas.beginPath();
+        var start = this.getLocalXY(geometry.components[0]);
+        this.canvas.moveTo(start[0], start[1]);
+        for(var i = 1; i < geometry.components.length - 1 ; i++) {
+            var pt = this.getLocalXY(geometry.components[i]);
+            this.canvas.lineTo(pt[0], pt[1]);
+        }
+        this.canvas.fill();
+        
+        var oldWidth = this.canvas.lineWidth; 
+        this.setCanvasStyle("stroke", style);
+        this.canvas.beginPath();
+        var start = this.getLocalXY(geometry.components[0]);
+        this.canvas.moveTo(start[0], start[1]);
+        for(var i = 1; i < geometry.components.length; i++) {
+            var pt = this.getLocalXY(geometry.components[i]);
+            this.canvas.lineTo(pt[0], pt[1]);
+        }
+        this.canvas.stroke();
+        this.setCanvasStyle("reset");
+    },    
+    
+    /**
+     * Method: drawPolygon
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>}
+     * style    - {Object}
+     */ 
+    drawPolygon: function(geometry, style) {
+        this.drawLinearRing(geometry.components[0], style);
+        for (var i = 1; i < geometry.components.length; i++) {
+            this.drawLinearRing(geometry.components[i], {
+                fillOpacity: 0, 
+                strokeWidth: 0, 
+                strokeOpacity: 0, 
+                strokeColor: '#000000', 
+                fillColor: '#000000'}
+            ); // inner rings are 'empty'  
+        }
+    },
+
+    /**
+     * Method: getLocalXY
+     * transform geographic xy into pixel xy
+     *
+     * Parameters: 
+     * point - {<OpenLayers.Geometry.Point>}
+     */
+    getLocalXY: function(point) {
+        var resolution = this.getResolution();
+        var extent = this.extent;
+        var x = (point.x / resolution + (-extent.left / resolution));
+        var y = ((extent.top / resolution) - point.y / resolution);
+        return [x, y];
+    },
+        
+    /**
+     * Method: clear
+     * Clear all vectors from the renderer.
+     * virtual function.
+     */    
+    clear: function() {
+        this.canvas.clearRect(0, 0, this.root.width, this.root.height);
+    },
+
+    /**
+     * Method: getFeatureIdFromEvent
+     * Returns a feature id from an event on the renderer.  
+     * 
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     *
+     * Returns:
+     * {String} A feature id or null.
+     */
+    getFeatureIdFromEvent: function(evt) {
+        var loc = this.map.getLonLatFromPixel(evt.xy);
+        var resolution = this.getResolution();
+        var bounds = new OpenLayers.Bounds(loc.lon - resolution * 5, 
+                                           loc.lat - resolution * 5, 
+                                           loc.lon + resolution * 5, 
+                                           loc.lat + resolution * 5);
+        var geom = bounds.toGeometry();
+        for (var feat in this.features) {
+            if (!this.features.hasOwnProperty(feat)) { continue; }
+            if (this.features[feat][0].geometry.intersects(geom)) {
+                return feat;
+            }
+        }   
+        return null;
+    },
+    
+    /**
+     * Method: eraseFeatures 
+     * This is called by the layer to erase features; removes the feature from
+     *     the list, then redraws the layer.
+     * 
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    eraseFeatures: function(features) {
+        if(!(features instanceof Array)) {
+            features = [features];
+        }
+        for(var i=0; i<features.length; ++i) {
+            delete this.features[features[i].id];
+        }
+        this.redraw();
+    },
+
+    /**
+     * Method: redraw
+     * The real 'meat' of the function: any time things have changed,
+     *     redraw() can be called to loop over all the data and (you guessed
+     *     it) redraw it.  Unlike Elements-based Renderers, we can't interact
+     *     with things once they're drawn, to remove them, for example, so
+     *     instead we have to just clear everything and draw from scratch.
+     */
+    redraw: function() {
+        if (!this.locked) {
+            this.clear();
+            for (var id in this.features) {
+                if (!this.features.hasOwnProperty(id)) { continue; }
+                if (!this.features[id][0].geometry) { continue; }
+                this.drawGeometry(this.features[id][0].geometry, this.features[id][1]);
+            }
+        }    
+    },
+
+    CLASS_NAME: "OpenLayers.Renderer.Canvas"
+});
+/* ======================================================================
+    OpenLayers/Renderer/Elements.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer.js
+ */
+
+/**
+ * Class: OpenLayers.ElementsIndexer
+ * This class takes care of figuring out which order elements should be
+ *     placed in the DOM based on given indexing methods. 
+ */
+OpenLayers.ElementsIndexer = OpenLayers.Class({
+   
+    /**
+     * Property: maxZIndex
+     * {Integer} This is the largest-most z-index value for a node
+     *     contained within the indexer.
+     */
+    maxZIndex: null,
+    
+    /**
+     * Property: order
+     * {Array<String>} This is an array of node id's stored in the
+     *     order that they should show up on screen. Id's higher up in the
+     *     array (higher array index) represent nodes with higher z-indeces.
+     */
+    order: null, 
+    
+    /**
+     * Property: indices
+     * {Object} This is a hash that maps node ids to their z-index value
+     *     stored in the indexer. This is done to make finding a nodes z-index 
+     *     value O(1).
+     */
+    indices: null,
+    
+    /**
+     * Property: compare
+     * {Function} This is the function used to determine placement of
+     *     of a new node within the indexer. If null, this defaults to to
+     *     the Z_ORDER_DRAWING_ORDER comparison method.
+     */
+    compare: null,
+   
+    /**
+     * APIMethod: initialize
+     * Create a new indexer with 
+     * 
+     * Parameters:
+     * yOrdering - {Boolean} Whether to use y-ordering.
+     */
+    initialize: function(yOrdering) {
+
+        this.compare = yOrdering ? 
+            OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_Y_ORDER :
+            OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER_DRAWING_ORDER;
+            
+        this.order = [];
+        this.indices = {};
+        this.maxZIndex = 0;
+    },
+    
+    /**
+     * APIMethod: insert
+     * Insert a new node into the indexer. In order to find the correct 
+     *     positioning for the node to be inserted, this method uses a binary 
+     *     search. This makes inserting O(log(n)). 
+     * 
+     * Parameters:
+     * newNode - {DOMElement} The new node to be inserted.
+     * 
+     * Returns
+     * {DOMElement} the node before which we should insert our newNode, or
+     *     null if newNode can just be appended.
+     */
+    insert: function(newNode) {
+        // If the node is known to the indexer, remove it so we can
+        // recalculate where it should go.
+        if (this.exists(newNode)) {
+            this.remove(newNode);
+        }
+        
+        var nodeId = newNode.id;
+        
+        this.determineZIndex(newNode);       
+
+        var leftIndex = -1;
+        var rightIndex = this.order.length;
+        var middle;
+
+        while (rightIndex - leftIndex > 1) {
+            middle = parseInt((leftIndex + rightIndex) / 2);
+            
+            var placement = this.compare(this, newNode,
+                OpenLayers.Util.getElement(this.order[middle]));
+            
+            if (placement > 0) {
+                leftIndex = middle;
+            } else {
+                rightIndex = middle;
+            } 
+        }
+        
+        this.order.splice(rightIndex, 0, nodeId);
+        this.indices[nodeId] = this.getZIndex(newNode);
+        
+        // If the new node should be before another in the index
+        // order, return the node before which we have to insert the new one;
+        // else, return null to indicate that the new node can be appended.
+        var nextIndex = rightIndex + 1;
+        return nextIndex < this.order.length ?
+                OpenLayers.Util.getElement(this.order[nextIndex]) : null;
+    },
+    
+    /**
+     * APIMethod: remove
+     * 
+     * Parameters:
+     * node - {DOMElement} The node to be removed.
+     */
+    remove: function(node) {
+        var nodeId = node.id;
+        var arrayIndex = OpenLayers.Util.indexOf(this.order, nodeId);
+        if (arrayIndex >= 0) {
+            // Remove it from the order array, as well as deleting the node
+            // from the indeces hash.
+            this.order.splice(arrayIndex, 1);
+            delete this.indices[nodeId];
+            
+            // Reset the maxium z-index based on the last item in the 
+            // order array.
+            if (this.order.length > 0) {
+                var lastId = this.order[this.order.length - 1];
+                this.maxZIndex = this.indices[lastId];
+            } else {
+                this.maxZIndex = 0;
+            }
+        }
+    },
+    
+    /**
+     * APIMethod: clear
+     */
+    clear: function() {
+        this.order = [];
+        this.indices = {};
+        this.maxZIndex = 0;
+    },
+    
+    /**
+     * APIMethod: exists
+     *
+     * Parameters:
+     * node- {DOMElement} The node to test for existence.
+     *
+     * Returns:
+     * {Boolean} Whether or not the node exists in the indexer?
+     */
+    exists: function(node) {
+        return (this.indices[node.id] != null);
+    },
+
+    /**
+     * APIMethod: getZIndex
+     * Get the z-index value for the current node from the node data itself.
+     * 
+     * Parameters:
+     * node - {DOMElement} The node whose z-index to get.
+     * 
+     * Returns:
+     * {Integer} The z-index value for the specified node (from the node 
+     *     data itself).
+     */
+    getZIndex: function(node) {
+        return node._style.graphicZIndex;  
+    },
+    
+    /**
+     * Method: determineZIndex
+     * Determine the z-index for the current node if there isn't one, 
+     *     and set the maximum value if we've found a new maximum.
+     * 
+     * Parameters:
+     * node - {DOMElement} 
+     */
+    determineZIndex: function(node) {
+        var zIndex = node._style.graphicZIndex;
+        
+        // Everything must have a zIndex. If none is specified,
+        // this means the user *must* (hint: assumption) want this
+        // node to succomb to drawing order. To enforce drawing order
+        // over all indexing methods, we'll create a new z-index that's
+        // greater than any currently in the indexer.
+        if (zIndex == null) {
+            zIndex = this.maxZIndex;
+            node._style.graphicZIndex = zIndex; 
+        } else if (zIndex > this.maxZIndex) {
+            this.maxZIndex = zIndex;
+        }
+    },
+    
+    CLASS_NAME: "OpenLayers.ElementsIndexer"
+});
+
+/**
+ * Namespace: OpenLayers.ElementsIndexer.IndexingMethods
+ * These are the compare methods for figuring out where a new node should be 
+ *     placed within the indexer. These methods are very similar to general 
+ *     sorting methods in that they return -1, 0, and 1 to specify the 
+ *     direction in which new nodes fall in the ordering.
+ */
+OpenLayers.ElementsIndexer.IndexingMethods = {
+    
+    /**
+     * Method: Z_ORDER
+     * This compare method is used by other comparison methods.
+     *     It can be used individually for ordering, but is not recommended,
+     *     because it doesn't subscribe to drawing order.
+     * 
+     * Parameters:
+     * indexer - {<OpenLayers.ElementsIndexer>}
+     * newNode - {DOMElement}
+     * nextNode - {DOMElement}
+     * 
+     * Returns:
+     * {Integer}
+     */
+    Z_ORDER: function(indexer, newNode, nextNode) {
+        var newZIndex = indexer.getZIndex(newNode);
+
+        var returnVal = 0;
+        if (nextNode) {
+            var nextZIndex = indexer.getZIndex(nextNode);
+            returnVal = newZIndex - nextZIndex; 
+        }
+        
+        return returnVal;
+    },
+
+    /**
+     * APIMethod: Z_ORDER_DRAWING_ORDER
+     * This method orders nodes by their z-index, but does so in a way
+     *     that, if there are other nodes with the same z-index, the newest 
+     *     drawn will be the front most within that z-index. This is the 
+     *     default indexing method.
+     * 
+     * Parameters:
+     * indexer - {<OpenLayers.ElementsIndexer>}
+     * newNode - {DOMElement}
+     * nextNode - {DOMElement}
+     * 
+     * Returns:
+     * {Integer}
+     */
+    Z_ORDER_DRAWING_ORDER: function(indexer, newNode, nextNode) {
+        var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+            indexer, 
+            newNode, 
+            nextNode
+        );
+        
+        // Make Z_ORDER subscribe to drawing order by pushing it above
+        // all of the other nodes with the same z-index.
+        if (nextNode && returnVal == 0) {
+            returnVal = 1;
+        }
+        
+        return returnVal;
+    },
+
+    /**
+     * APIMethod: Z_ORDER_Y_ORDER
+     * This one should really be called Z_ORDER_Y_ORDER_DRAWING_ORDER, as it
+     *     best describes which ordering methods have precedence (though, the 
+     *     name would be too long). This method orders nodes by their z-index, 
+     *     but does so in a way that, if there are other nodes with the same 
+     *     z-index, the nodes with the lower y position will be "closer" than 
+     *     those with a higher y position. If two nodes have the exact same y 
+     *     position, however, then this method will revert to using drawing  
+     *     order to decide placement.
+     * 
+     * Parameters:
+     * indexer - {<OpenLayers.ElementsIndexer>}
+     * newNode - {DOMElement}
+     * nextNode - {DOMElement}
+     * 
+     * Returns:
+     * {Integer}
+     */
+    Z_ORDER_Y_ORDER: function(indexer, newNode, nextNode) {
+        var returnVal = OpenLayers.ElementsIndexer.IndexingMethods.Z_ORDER(
+            indexer, 
+            newNode, 
+            nextNode
+        );
+        
+        if (nextNode && returnVal == 0) {
+            var newLat = newNode._geometry.getBounds().bottom;
+            var nextLat = nextNode._geometry.getBounds().bottom;
+            
+            var result = nextLat - newLat;
+            returnVal = (result ==0) ? 1 : result;
+        }
+        
+        return returnVal;       
+    }
+};
+
+/**
+ * Class: OpenLayers.Renderer.Elements
+ * This is another virtual class in that it should never be instantiated by 
+ *  itself as a Renderer. It exists because there is *tons* of shared 
+ *  functionality between different vector libraries which use nodes/elements
+ *  as a base for rendering vectors. 
+ * 
+ * The highlevel bits of code that are implemented here are the adding and 
+ *  removing of geometries, which is essentially the same for any 
+ *  element-based renderer. The details of creating each node and drawing the
+ *  paths are of course different, but the machinery is the same. 
+ * 
+ * Inherits:
+ *  - <OpenLayers.Renderer>
+ */
+OpenLayers.Renderer.Elements = OpenLayers.Class(OpenLayers.Renderer, {
+
+    /**
+     * Property: rendererRoot
+     * {DOMElement}
+     */
+    rendererRoot: null,
+    
+    /**
+     * Property: root
+     * {DOMElement}
+     */
+    root: null,
+
+    /**
+     * Property: xmlns
+     * {String}
+     */    
+    xmlns: null,
+    
+    /**
+     * Property: Indexer
+     * {<OpenLayers.ElementIndexer>} An instance of OpenLayers.ElementsIndexer 
+     *     created upon initialization if the zIndexing or yOrdering options
+     *     passed to this renderer's constructor are set to true.
+     */
+    indexer: null, 
+    
+    /**
+     * Constant: BACKGROUND_ID_SUFFIX
+     * {String}
+     */
+    BACKGROUND_ID_SUFFIX: "_background",
+    
+    /**
+     * Property: minimumSymbolizer
+     * {Object}
+     */
+    minimumSymbolizer: {
+        strokeLinecap: "round",
+        strokeOpacity: 1,
+        strokeDashstyle: "solid",
+        fillOpacity: 1,
+        pointRadius: 0
+    },
+    
+    /**
+     * Constructor: OpenLayers.Renderer.Elements
+     * 
+     * Parameters:
+     * containerID - {String}
+     * options - {Object} options for this renderer. Supported options are:
+     *     * yOrdering - {Boolean} Whether to use y-ordering
+     *     * zIndexing - {Boolean} Whether to use z-indexing. Will be ignored
+     *         if yOrdering is set to true.
+     */
+    initialize: function(containerID, options) {
+        OpenLayers.Renderer.prototype.initialize.apply(this, arguments);
+
+        this.rendererRoot = this.createRenderRoot();
+        this.root = this.createRoot();
+        
+        this.rendererRoot.appendChild(this.root);
+        this.container.appendChild(this.rendererRoot);
+        
+        if(options && (options.zIndexing || options.yOrdering)) {
+            this.indexer = new OpenLayers.ElementsIndexer(options.yOrdering);
+        }
+    },
+    
+    /**
+     * Method: destroy
+     */
+    destroy: function() {
+
+        this.clear(); 
+
+        this.rendererRoot = null;
+        this.root = null;
+        this.xmlns = null;
+
+        OpenLayers.Renderer.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * Method: clear
+     * Remove all the elements from the root
+     */    
+    clear: function() {
+        if (this.root) {
+            while (this.root.childNodes.length > 0) {
+                this.root.removeChild(this.root.firstChild);
+            }
+        }
+        if (this.indexer) {
+            this.indexer.clear();
+        }
+    },
+
+    /** 
+     * Method: getNodeType
+     * This function is in charge of asking the specific renderer which type
+     *     of node to create for the given geometry and style. All geometries
+     *     in an Elements-based renderer consist of one node and some
+     *     attributes. We have the nodeFactory() function which creates a node
+     *     for us, but it takes a 'type' as input, and that is precisely what
+     *     this function tells us.  
+     *  
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * 
+     * Returns:
+     * {String} The corresponding node type for the specified geometry
+     */
+    getNodeType: function(geometry, style) { },
+
+    /** 
+     * Method: drawGeometry 
+     * Draw the geometry, creating new nodes, setting paths, setting style,
+     *     setting featureId on the node.  This method should only be called
+     *     by the renderer itself.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * featureId - {String}
+     * 
+     * Returns:
+     * {Boolean} true if the geometry has been drawn completely; null if
+     *     incomplete; false otherwise
+     */
+    drawGeometry: function(geometry, style, featureId) {
+        var className = geometry.CLASS_NAME;
+        var rendered = true;
+        if ((className == "OpenLayers.Geometry.Collection") ||
+            (className == "OpenLayers.Geometry.MultiPoint") ||
+            (className == "OpenLayers.Geometry.MultiLineString") ||
+            (className == "OpenLayers.Geometry.MultiPolygon")) {
+            for (var i = 0, len=geometry.components.length; i<len; i++) {
+                rendered = rendered && this.drawGeometry(
+                    geometry.components[i], style, featureId);
+            }
+            return rendered;
+        };
+
+        rendered = false;
+        if (style.display != "none") {
+            if (style.backgroundGraphic) {
+                this.redrawBackgroundNode(geometry.id, geometry, style,
+                    featureId);
+            }
+            rendered = this.redrawNode(geometry.id, geometry, style,
+                featureId);
+        }
+        if (rendered == false) {
+            var node = document.getElementById(geometry.id);
+            if (node) {
+                if (node._style.backgroundGraphic) {
+                    node.parentNode.removeChild(document.getElementById(
+                        geometry.id + this.BACKGROUND_ID_SUFFIX));
+                }
+                node.parentNode.removeChild(node);
+            }
+        }
+        return rendered;
+    },
+    
+    /**
+     * Method: redrawNode
+     * 
+     * Parameters:
+     * id - {String}
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * featureId - {String}
+     * 
+     * Returns:
+     * {Boolean} true if the complete geometry could be drawn, null if parts of
+     *     the geometry could not be drawn, false otherwise
+     */
+    redrawNode: function(id, geometry, style, featureId) {
+        // Get the node if it's already on the map.
+        var currentNode = OpenLayers.Util.getElement(id);
+        
+        // Create a new node, or use the current one if it's
+        // already there.
+        var newNode;
+        if (!currentNode) {
+            var nodeType = this.getNodeType(geometry, style);
+            newNode = this.createNode(nodeType, id);
+        } else {
+            newNode = currentNode;
+        }
+        
+        // Set the data for the node, then draw it.
+        newNode._featureId = featureId;
+        newNode._geometry = geometry;
+        newNode._geometryClass = geometry.CLASS_NAME;
+        newNode._style = style;
+
+        var drawResult = this.drawGeometryNode(newNode, geometry, style);
+        if(drawResult === false) {
+            return false;
+        }
+         
+        newNode = drawResult.node;
+        
+        // Insert the node into the indexer so it can show us where to
+        // place it. Note that this operation is O(log(n)). If there's a
+        // performance problem (when dragging, for instance) this is
+        // likely where it would be.
+        var insert = this.indexer ? this.indexer.insert(newNode) : null;
+
+        if(insert) {
+            this.root.insertBefore(newNode, insert);
+        } else {
+            this.root.appendChild(newNode);
+        }
+        
+        this.postDraw(newNode);
+        
+        return drawResult.complete;
+    },
+    
+    /**
+     * Method: redrawBackgroundNode
+     * Redraws the node using special 'background' style properties. Basically
+     *     just calls redrawNode(), but instead of directly using the 
+     *     'externalGraphic', 'graphicXOffset', 'graphicYOffset', and 
+     *     'graphicZIndex' properties directly from the specified 'style' 
+     *     parameter, we create a new style object and set those properties 
+     *     from the corresponding 'background'-prefixed properties from 
+     *     specified 'style' parameter.
+     * 
+     * Parameters:
+     * id - {String}
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * featureId - {String}
+     * 
+     * Returns:
+     * {Boolean} true if the complete geometry could be drawn, null if parts of
+     *     the geometry could not be drawn, false otherwise
+     */
+    redrawBackgroundNode: function(id, geometry, style, featureId) {
+        var backgroundStyle = OpenLayers.Util.extend({}, style);
+        
+        // Set regular style attributes to apply to the background styles.
+        backgroundStyle.externalGraphic = backgroundStyle.backgroundGraphic;
+        backgroundStyle.graphicXOffset = backgroundStyle.backgroundXOffset;
+        backgroundStyle.graphicYOffset = backgroundStyle.backgroundYOffset;
+        backgroundStyle.graphicZIndex = backgroundStyle.backgroundGraphicZIndex;
+        
+        // Erase background styles.
+        backgroundStyle.backgroundGraphic = null;
+        backgroundStyle.backgroundXOffset = null;
+        backgroundStyle.backgroundYOffset = null;
+        backgroundStyle.backgroundGraphicZIndex = null;
+        
+        return this.redrawNode(
+            id + this.BACKGROUND_ID_SUFFIX, 
+            geometry, 
+            backgroundStyle, 
+            null
+        );
+    },
+
+    /**
+     * Method: drawGeometryNode
+     * Given a node, draw a geometry on the specified layer.
+     *     node and geometry are required arguments, style is optional.
+     *     This method is only called by the render itself.
+     *
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * 
+     * Returns:
+     * {Object} a hash with properties "node" (the drawn node) and "complete"
+     *     (null if parts of the geometry could not be drawn, false if nothing
+     *     could be drawn)
+     */
+    drawGeometryNode: function(node, geometry, style) {
+        style = style || node._style;
+        OpenLayers.Util.applyDefaults(style, this.minimumSymbolizer);
+
+        var options = {
+            'isFilled': true,
+            'isStroked': !!style.strokeWidth
+        };
+        var drawn;
+        switch (geometry.CLASS_NAME) {
+            case "OpenLayers.Geometry.Point":
+                drawn = this.drawPoint(node, geometry);
+                break;
+            case "OpenLayers.Geometry.LineString":
+                options.isFilled = false;
+                drawn = this.drawLineString(node, geometry);
+                break;
+            case "OpenLayers.Geometry.LinearRing":
+                drawn = this.drawLinearRing(node, geometry);
+                break;
+            case "OpenLayers.Geometry.Polygon":
+                drawn = this.drawPolygon(node, geometry);
+                break;
+            case "OpenLayers.Geometry.Surface":
+                drawn = this.drawSurface(node, geometry);
+                break;
+            case "OpenLayers.Geometry.Rectangle":
+                drawn = this.drawRectangle(node, geometry);
+                break;
+            default:
+                break;
+        }
+
+        node._style = style; 
+        node._options = options; 
+
+        //set style
+        //TBD simplify this
+        if (drawn != false) {
+            return {
+                node: this.setStyle(node, style, options, geometry),
+                complete: drawn
+            };
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: postDraw
+     * Things that have do be done after the geometry node is appended
+     *     to its parent node. To be overridden by subclasses.
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     */
+    postDraw: function(node) {},
+    
+    /**
+     * Method: drawPoint
+     * Virtual function for drawing Point Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the point
+     */ 
+    drawPoint: function(node, geometry) {},
+
+    /**
+     * Method: drawLineString
+     * Virtual function for drawing LineString Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components of
+     *     the linestring, or false if nothing could be drawn
+     */ 
+    drawLineString: function(node, geometry) {},
+
+    /**
+     * Method: drawLinearRing
+     * Virtual function for drawing LinearRing Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components
+     *     of the linear ring, or false if nothing could be drawn
+     */ 
+    drawLinearRing: function(node, geometry) {},
+
+    /**
+     * Method: drawPolygon
+     * Virtual function for drawing Polygon Geometry. 
+     *    Should be implemented by subclasses.
+     *    This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components
+     *     of the polygon, or false if nothing could be drawn
+     */ 
+    drawPolygon: function(node, geometry) {},
+
+    /**
+     * Method: drawRectangle
+     * Virtual function for drawing Rectangle Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the rectangle
+     */ 
+    drawRectangle: function(node, geometry) {},
+
+    /**
+     * Method: drawCircle
+     * Virtual function for drawing Circle Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the circle
+     */ 
+    drawCircle: function(node, geometry) {},
+
+    /**
+     * Method: drawSurface
+     * Virtual function for drawing Surface Geometry. 
+     *     Should be implemented by subclasses.
+     *     This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the surface
+     */ 
+    drawSurface: function(node, geometry) {},
+
+    /**
+     * Method: getFeatureIdFromEvent
+     * 
+     * Parameters:
+     * evt - {Object} An <OpenLayers.Event> object
+     *
+     * Returns:
+     * {<OpenLayers.Geometry>} A geometry from an event that 
+     *     happened on a layer.
+     */
+    getFeatureIdFromEvent: function(evt) {
+        var target = evt.target;
+        var useElement = target && target.correspondingUseElement;
+        var node = useElement ? useElement : (target || evt.srcElement);
+        var featureId = node._featureId;
+        return featureId;
+    },
+
+    /** 
+     * Method: eraseGeometry
+     * Erase a geometry from the renderer. In the case of a multi-geometry, 
+     *     we cycle through and recurse on ourselves. Otherwise, we look for a 
+     *     node with the geometry.id, destroy its geometry, and remove it from
+     *     the DOM.
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     */
+    eraseGeometry: function(geometry) {
+        if ((geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPoint") ||
+            (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiLineString") ||
+            (geometry.CLASS_NAME == "OpenLayers.Geometry.MultiPolygon") ||
+            (geometry.CLASS_NAME == "OpenLayers.Geometry.Collection")) {
+            for (var i=0, len=geometry.components.length; i<len; i++) {
+                this.eraseGeometry(geometry.components[i]);
+            }
+        } else {    
+            var element = OpenLayers.Util.getElement(geometry.id);
+            if (element && element.parentNode) {
+                if (element.geometry) {
+                    element.geometry.destroy();
+                    element.geometry = null;
+                }
+                element.parentNode.removeChild(element);
+
+                if (this.indexer) {
+                    this.indexer.remove(element);
+                    
+                    var backgroundId = geometry.id + this.BACKGROUND_ID_SUFFIX;
+                    var bElem = OpenLayers.Util.getElement(backgroundId);
+                    if (bElem && bElem.parentNode) {
+                        // No need to destroy the geometry since the element and the background
+                        // node share the same geometry.
+                        bElem.parentNode.removeChild(bElem);
+                    }
+                }
+            }
+        }
+    },
+
+    /** 
+     * Method: nodeFactory
+     * Create new node of the specified type, with the (optional) specified id.
+     * 
+     * If node already exists with same ID and a different type, we remove it
+     *     and then call ourselves again to recreate it.
+     * 
+     * Parameters:
+     * id - {String}
+     * type - {String} type Kind of node to draw.
+     * 
+     * Returns:
+     * {DOMElement} A new node of the given type and id.
+     */
+    nodeFactory: function(id, type) {
+        var node = OpenLayers.Util.getElement(id);
+        if (node) {
+            if (!this.nodeTypeCompare(node, type)) {
+                node.parentNode.removeChild(node);
+                node = this.nodeFactory(id, type);
+            }
+        } else {
+            node = this.createNode(type, id);
+        }
+        return node;
+    },
+    
+    /** 
+     * Method: createNode
+     * 
+     * Parameters:
+     * type - {String} Kind of node to draw.
+     * id - {String} Id for node.
+     * 
+     * Returns:
+     * {DOMElement} A new node of the given type and id.
+     *     This function must be overridden by subclasses.
+     */
+    createNode: function(type, id) {},
+
+    /**
+     * Method: isComplexSymbol
+     * Determines if a symbol cannot be rendered using drawCircle
+     * 
+     * Parameters:
+     * graphicName - {String}
+     * 
+     * Returns
+     * {Boolean} true if the symbol is complex, false if not
+     */
+    isComplexSymbol: function(graphicName) {
+        return (graphicName != "circle") && !!graphicName;
+    },
+
+    CLASS_NAME: "OpenLayers.Renderer.Elements"
+});
+
+
+/**
+ * Constant: OpenLayers.Renderer.symbol
+ * Coordinate arrays for well known (named) symbols.
+ */
+OpenLayers.Renderer.symbol = {
+    "star": [350,75, 379,161, 469,161, 397,215, 423,301, 350,250, 277,301,
+            303,215, 231,161, 321,161, 350,75],
+    "cross": [4,0, 6,0, 6,4, 10,4, 10,6, 6,6, 6,10, 4,10, 4,6, 0,6, 0,4, 4,4,
+            4,0],
+    "x": [0,0, 25,0, 50,35, 75,0, 100,0, 65,50, 100,100, 75,100, 50,65, 25,100, 0,100, 35,50, 0,0],
+    "square": [0,0, 0,1, 1,1, 1,0, 0,0],
+    "triangle": [0,10, 10,10, 5,0, 0,10]
+};
+/* ======================================================================
+    OpenLayers/Request/XMLHttpRequest.js
+   ====================================================================== */
+
+// 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
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+/**
+ * @requires OpenLayers/Request.js
+ */
+
+(function () {
+
+    // Save reference to earlier defined object implementation (if any)
+    var oXMLHttpRequest    = window.XMLHttpRequest;
+
+    // Define on browser type
+    var bGecko    = !!window.controllers,
+        bIE        = window.document.all && !window.opera;
+
+    // Constructor
+    function cXMLHttpRequest() {
+        this._object    = oXMLHttpRequest ? new oXMLHttpRequest : new window.ActiveXObject('Microsoft.XMLHTTP');
+    };
+
+    // BUGFIX: Firefox with Firebug installed would break pages if not executed
+    if (bGecko && oXMLHttpRequest.wrapped)
+        cXMLHttpRequest.wrapped    = oXMLHttpRequest.wrapped;
+
+    // Constants
+    cXMLHttpRequest.UNSENT                = 0;
+    cXMLHttpRequest.OPENED                = 1;
+    cXMLHttpRequest.HEADERS_RECEIVED    = 2;
+    cXMLHttpRequest.LOADING                = 3;
+    cXMLHttpRequest.DONE                = 4;
+
+    // Public Properties
+    cXMLHttpRequest.prototype.readyState    = cXMLHttpRequest.UNSENT;
+    cXMLHttpRequest.prototype.responseText    = "";
+    cXMLHttpRequest.prototype.responseXML    = null;
+    cXMLHttpRequest.prototype.status        = 0;
+    cXMLHttpRequest.prototype.statusText    = "";
+
+    // Instance-level Events Handlers
+    cXMLHttpRequest.prototype.onreadystatechange    = null;
+
+    // Class-level Events Handlers
+    cXMLHttpRequest.onreadystatechange    = null;
+    cXMLHttpRequest.onopen                = null;
+    cXMLHttpRequest.onsend                = null;
+    cXMLHttpRequest.onabort                = null;
+
+    // Public Methods
+    cXMLHttpRequest.prototype.open    = function(sMethod, sUrl, bAsync, sUser, sPassword) {
+
+        // Save async parameter for fixing Gecko bug with missing readystatechange in synchronous requests
+        this._async        = bAsync;
+
+        // Set the onreadystatechange handler
+        var oRequest    = this,
+            nState        = this.readyState;
+
+        // BUGFIX: IE - memory leak on page unload (inter-page leak)
+        if (bIE) {
+            var fOnUnload    = function() {
+                if (oRequest._object.readyState != cXMLHttpRequest.DONE)
+                    fCleanTransport(oRequest);
+            };
+            if (bAsync)
+                window.attachEvent("onunload", fOnUnload);
+        }
+
+        this._object.onreadystatechange    = function() {
+            if (bGecko && !bAsync)
+                return;
+
+            // Synchronize state
+            oRequest.readyState        = oRequest._object.readyState;
+
+            //
+            fSynchronizeValues(oRequest);
+
+            // BUGFIX: Firefox fires unneccesary DONE when aborting
+            if (oRequest._aborted) {
+                // Reset readyState to UNSENT
+                oRequest.readyState    = cXMLHttpRequest.UNSENT;
+
+                // Return now
+                return;
+            }
+
+            if (oRequest.readyState == cXMLHttpRequest.DONE) {
+                //
+                fCleanTransport(oRequest);
+// Uncomment this block if you need a fix for IE cache
+/*
+                // BUGFIX: IE - cache issue
+                if (!oRequest._object.getResponseHeader("Date")) {
+                    // Save object to cache
+                    oRequest._cached    = oRequest._object;
+
+                    // Instantiate a new transport object
+                    cXMLHttpRequest.call(oRequest);
+
+                    // Re-send request
+                    oRequest._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+                    oRequest._object.setRequestHeader("If-Modified-Since", oRequest._cached.getResponseHeader("Last-Modified") || new window.Date(0));
+                    // Copy headers set
+                    if (oRequest._headers)
+                        for (var sHeader in oRequest._headers)
+                            if (typeof oRequest._headers[sHeader] == "string")    // Some frameworks prototype objects with functions
+                                oRequest._object.setRequestHeader(sHeader, oRequest._headers[sHeader]);
+
+                    oRequest._object.onreadystatechange    = function() {
+                        // Synchronize state
+                        oRequest.readyState        = oRequest._object.readyState;
+
+                        if (oRequest._aborted) {
+                            //
+                            oRequest.readyState    = cXMLHttpRequest.UNSENT;
+
+                            // Return
+                            return;
+                        }
+
+                        if (oRequest.readyState == cXMLHttpRequest.DONE) {
+                            // Clean Object
+                            fCleanTransport(oRequest);
+
+                            // get cached request
+                            if (oRequest.status == 304)
+                                oRequest._object    = oRequest._cached;
+
+                            //
+                            delete oRequest._cached;
+
+                            //
+                            fSynchronizeValues(oRequest);
+
+                            //
+                            fReadyStateChange(oRequest);
+
+                            // BUGFIX: IE - memory leak in interrupted
+                            if (bIE && bAsync)
+                                window.detachEvent("onunload", fOnUnload);
+                        }
+                    };
+                    oRequest._object.send(null);
+
+                    // Return now - wait untill re-sent request is finished
+                    return;
+                };
+*/
+                // BUGFIX: IE - memory leak in interrupted
+                if (bIE && bAsync)
+                    window.detachEvent("onunload", fOnUnload);
+            }
+
+            // BUGFIX: Some browsers (Internet Explorer, Gecko) fire OPEN readystate twice
+            if (nState != oRequest.readyState)
+                fReadyStateChange(oRequest);
+
+            nState    = oRequest.readyState;
+        };
+
+        // Add method sniffer
+        if (cXMLHttpRequest.onopen)
+            cXMLHttpRequest.onopen.apply(this, arguments);
+
+        this._object.open(sMethod, sUrl, bAsync, sUser, sPassword);
+
+        // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
+        if (!bAsync && bGecko) {
+            this.readyState    = cXMLHttpRequest.OPENED;
+
+            fReadyStateChange(this);
+        }
+    };
+    cXMLHttpRequest.prototype.send    = function(vData) {
+        // Add method sniffer
+        if (cXMLHttpRequest.onsend)
+            cXMLHttpRequest.onsend.apply(this, arguments);
+
+        // BUGFIX: Safari - fails sending documents created/modified dynamically, so an explicit serialization required
+        // BUGFIX: IE - rewrites any custom mime-type to "text/xml" in case an XMLNode is sent
+        // BUGFIX: Gecko - fails sending Element (this is up to the implementation either to standard)
+        if (vData && vData.nodeType) {
+            vData    = window.XMLSerializer ? new window.XMLSerializer().serializeToString(vData) : vData.xml;
+            if (!this._headers["Content-Type"])
+                this._object.setRequestHeader("Content-Type", "application/xml");
+        }
+
+        this._object.send(vData);
+
+        // BUGFIX: Gecko - missing readystatechange calls in synchronous requests
+        if (bGecko && !this._async) {
+            this.readyState    = cXMLHttpRequest.OPENED;
+
+            // Synchronize state
+            fSynchronizeValues(this);
+
+            // Simulate missing states
+            while (this.readyState < cXMLHttpRequest.DONE) {
+                this.readyState++;
+                fReadyStateChange(this);
+                // Check if we are aborted
+                if (this._aborted)
+                    return;
+            }
+        }
+    };
+    cXMLHttpRequest.prototype.abort    = function() {
+        // Add method sniffer
+        if (cXMLHttpRequest.onabort)
+            cXMLHttpRequest.onabort.apply(this, arguments);
+
+        // BUGFIX: Gecko - unneccesary DONE when aborting
+        if (this.readyState > cXMLHttpRequest.UNSENT)
+            this._aborted    = true;
+
+        this._object.abort();
+
+        // BUGFIX: IE - memory leak
+        fCleanTransport(this);
+    };
+    cXMLHttpRequest.prototype.getAllResponseHeaders    = function() {
+        return this._object.getAllResponseHeaders();
+    };
+    cXMLHttpRequest.prototype.getResponseHeader    = function(sName) {
+        return this._object.getResponseHeader(sName);
+    };
+    cXMLHttpRequest.prototype.setRequestHeader    = function(sName, sValue) {
+        // BUGFIX: IE - cache issue
+        if (!this._headers)
+            this._headers    = {};
+        this._headers[sName]    = sValue;
+
+        return this._object.setRequestHeader(sName, sValue);
+    };
+    cXMLHttpRequest.prototype.toString    = function() {
+        return '[' + "object" + ' ' + "XMLHttpRequest" + ']';
+    };
+    cXMLHttpRequest.toString    = function() {
+        return '[' + "XMLHttpRequest" + ']';
+    };
+
+    // Helper function
+    function fReadyStateChange(oRequest) {
+        // Execute onreadystatechange
+        if (oRequest.onreadystatechange)
+            oRequest.onreadystatechange.apply(oRequest);
+
+        // Sniffing code
+        if (cXMLHttpRequest.onreadystatechange)
+            cXMLHttpRequest.onreadystatechange.apply(oRequest);
+    };
+
+    function fGetDocument(oRequest) {
+        var oDocument    = oRequest.responseXML;
+        // Try parsing responseText
+        if (bIE && oDocument && !oDocument.documentElement && oRequest.getResponseHeader("Content-Type").match(/[^\/]+\/[^\+]+\+xml/)) {
+            oDocument    = new ActiveXObject('Microsoft.XMLDOM');
+            oDocument.loadXML(oRequest.responseText);
+        }
+        // Check if there is no error in document
+        if (oDocument)
+            if ((bIE && oDocument.parseError != 0) || (oDocument.documentElement && oDocument.documentElement.tagName == "parsererror"))
+                return null;
+        return oDocument;
+    };
+
+    function fSynchronizeValues(oRequest) {
+        try {    oRequest.responseText    = oRequest._object.responseText;    } catch (e) {}
+        try {    oRequest.responseXML    = fGetDocument(oRequest._object);    } catch (e) {}
+        try {    oRequest.status            = oRequest._object.status;            } catch (e) {}
+        try {    oRequest.statusText        = oRequest._object.statusText;        } catch (e) {}
+    };
+
+    function fCleanTransport(oRequest) {
+        // BUGFIX: IE - memory leak (on-page leak)
+        oRequest._object.onreadystatechange    = new window.Function;
+
+        // Delete private properties
+        delete oRequest._headers;
+    };
+
+    // Internet Explorer 5.0 (missing apply)
+    if (!window.Function.prototype.apply) {
+        window.Function.prototype.apply    = function(oRequest, oArguments) {
+            if (!oArguments)
+                oArguments    = [];
+            oRequest.__func    = this;
+            oRequest.__func(oArguments[0], oArguments[1], oArguments[2], oArguments[3], oArguments[4]);
+            delete oRequest.__func;
+        };
+    };
+
+    // Register new object with window
+    /**
+     * Class: OpenLayers.Request.XMLHttpRequest
+     * Standard-compliant (W3C) cross-browser implementation of the
+     *     XMLHttpRequest object.  From
+     *     http://code.google.com/p/xmlhttprequest/.
+     */
+    OpenLayers.Request.XMLHttpRequest = cXMLHttpRequest;
+})();
+/* ======================================================================
+    OpenLayers/Strategy/Fixed.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Strategy.js
+ */
+
+/**
+ * Class: OpenLayers.Strategy.Fixed
+ * A simple strategy that requests features once and never requests new data.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Strategy>
+ */
+OpenLayers.Strategy.Fixed = OpenLayers.Class(OpenLayers.Strategy, {
+
+    /**
+     * Constructor: OpenLayers.Strategy.Fixed
+     * Create a new Fixed strategy.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Strategy.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: destroy
+     * Clean up the strategy.
+     */
+    destroy: function() {
+        OpenLayers.Strategy.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * Method: activate
+     * Activate the strategy: reads all features from the protocol and add them 
+     * to the layer.
+     *
+     * Returns:
+     * {Boolean} True if the strategy was successfully activated or false if
+     *      the strategy was already active.
+     */
+    activate: function() {
+        if(OpenLayers.Strategy.prototype.activate.apply(this, arguments)) {
+            this.layer.protocol.read({
+                callback: this.merge,
+                scope: this
+            });
+            return true;
+        }
+        return false;
+    },
+
+    /**
+     * Method: merge
+     * Add all features to the layer.
+     */
+    merge: function(resp) {
+        var features = resp.features;
+        if (features && features.length > 0) {
+            this.layer.addFeatures(features);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Strategy.Fixed"
+});
+/* ======================================================================
     OpenLayers/Tile.js
    ====================================================================== */
 
@@ -7282,39 +13276,7 @@
      * {Boolean} Is the tile loading?
      */
     isLoading: false,
-    
-    /**
-     * Property: isBackBuffer
-     * {Boolean} Is this tile a back buffer tile?
-     */
-    isBackBuffer: false,
-    
-    /**
-     * Property: lastRatio
-     * {Float} Used in transition code only.  This is the previous ratio
-     *     of the back buffer tile resolution to the map resolution.  Compared
-     *     with the current ratio to determine if zooming occurred.
-     */
-    lastRatio: 1,
-
-    /**
-     * Property: isFirstDraw
-     * {Boolean} Is this the first time the tile is being drawn?
-     *     This is used to force resetBackBuffer to synchronize
-     *     the backBufferTile with the foreground tile the first time
-     *     the foreground tile loads so that if the user zooms
-     *     before the layer has fully loaded, the backBufferTile for
-     *     tiles that have been loaded can be used.
-     */
-    isFirstDraw: true,
         
-    /**
-     * Property: backBufferTile
-     * {<OpenLayers.Tile>} A clone of the tile used to create transition
-     *     effects when the tile is moved or changes resolution.
-     */
-    backBufferTile: null,
-        
     /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor.
      *             there is no need for the base tile class to have a url.
      * 
@@ -7360,13 +13322,6 @@
      * Nullify references to prevent circular references and memory leaks.
      */
     destroy:function() {
-        if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, 
-                this.layer.transitionEffect) != -1) {
-            this.layer.events.unregister("loadend", this, this.resetBackBuffer);
-            this.events.unregister('loadend', this, this.resetBackBuffer);            
-        } else {
-            this.events.unregister('loadend', this, this.showTile);
-        }
         this.layer  = null;
         this.bounds = null;
         this.size = null;
@@ -7374,12 +13329,6 @@
         
         this.events.destroy();
         this.events = null;
-        
-        /* clean up the backBufferTile if it exists */
-        if (this.backBufferTile) {
-            this.backBufferTile.destroy();
-            this.backBufferTile = null;
-        }
     },
     
     /**
@@ -7424,50 +13373,12 @@
  
         // The only case where we *wouldn't* want to draw the tile is if the 
         // tile is outside its layer's maxExtent.
-        var drawTile = (withinMaxExtent || this.layer.displayOutsideMaxExtent);
-
-        if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, this.layer.transitionEffect) != -1) {
-            if (drawTile) {
-                //we use a clone of this tile to create a double buffer for visual
-                //continuity.  The backBufferTile is used to create transition
-                //effects while the tile in the grid is repositioned and redrawn
-                if (!this.backBufferTile) {
-                    this.backBufferTile = this.clone();
-                    this.backBufferTile.hide();
-                    // this is important.  It allows the backBuffer to place itself
-                    // appropriately in the DOM.  The Image subclass needs to put
-                    // the backBufferTile behind the main tile so the tiles can
-                    // load over top and display as soon as they are loaded.
-                    this.backBufferTile.isBackBuffer = true;
-                    
-                    // potentially end any transition effects when the tile loads
-                    this.events.register('loadend', this, this.resetBackBuffer);
-                    
-                    // clear transition back buffer tile only after all tiles in
-                    // this layer have loaded to avoid visual glitches
-                    this.layer.events.register("loadend", this, this.resetBackBuffer);
-                }
-                // run any transition effects
-                this.startTransition();
-            } else {
-                // if we aren't going to draw the tile, then the backBuffer should
-                // be hidden too!
-                if (this.backBufferTile) {
-                    this.backBufferTile.clear();
-                }
-            }
-        } else {
-            if (drawTile && this.isFirstDraw) {
-                this.events.register('loadend', this, this.showTile);
-                this.isFirstDraw = false;
-            }   
-        }    
-        this.shouldDraw = drawTile;
-        
+        this.shouldDraw = (withinMaxExtent || this.layer.displayOutsideMaxExtent);
+                
         //clear tile's contents and mark as not drawn
         this.clear();
         
-        return drawTile;
+        return this.shouldDraw;
     },
     
     /** 
@@ -7538,49 +13449,6 @@
                                        topLeft.lat);  
         return bounds;
     },        
-    
-    /** 
-     * Method: startTransition
-     * Prepare the tile for a transition effect.  To be
-     *     implemented by subclasses.
-     */
-    startTransition: function() {},
-    
-    /** 
-     * Method: resetBackBuffer
-     * Triggered by two different events, layer loadend, and tile loadend.
-     *     In any of these cases, we check to see if we can hide the 
-     *     backBufferTile yet and update its parameters to match the 
-     *     foreground tile.
-     *
-     * Basic logic:
-     *  - If the backBufferTile hasn't been drawn yet, reset it
-     *  - If layer is still loading, show foreground tile but don't hide
-     *    the backBufferTile yet
-     *  - If layer is done loading, reset backBuffer tile and show 
-     *    foreground tile
-     */
-    resetBackBuffer: function() {
-        this.showTile();
-        if (this.backBufferTile && 
-            (this.isFirstDraw || !this.layer.numLoadingTiles)) {
-            this.isFirstDraw = false;
-            // check to see if the backBufferTile is within the max extents
-            // before rendering it 
-            var maxExtent = this.layer.maxExtent;
-            var withinMaxExtent = (maxExtent &&
-                                   this.bounds.intersectsBounds(maxExtent, false));
-            if (withinMaxExtent) {
-                this.backBufferTile.position = this.position;
-                this.backBufferTile.bounds = this.bounds;
-                this.backBufferTile.size = this.size;
-                this.backBufferTile.imageSize = this.layer.imageSize || this.size;
-                this.backBufferTile.imageOffset = this.layer.imageOffset;
-                this.backBufferTile.resolution = this.layer.getResolution();
-                this.backBufferTile.renderTile();
-            }
-        }
-    },
         
     /** 
      * Method: showTile
@@ -7607,6 +13475,2440 @@
     CLASS_NAME: "OpenLayers.Tile"
 });
 /* ======================================================================
+    OpenLayers/Ajax.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+OpenLayers.ProxyHost = "";
+//OpenLayers.ProxyHost = "examples/proxy.cgi?url=";
+
+/**
+ * Ajax reader for OpenLayers
+ *
+ *  @uri url to do remote XML http get
+ *  @param {String} 'get' format params (x=y&a=b...)
+ *  @who object to handle callbacks for this request
+ *  @complete  the function to be called on success 
+ *  @failure  the function to be called on failure
+ *  
+ *   example usage from a caller:
+ *  
+ *     caps: function(request) {
+ *      -blah-  
+ *     },
+ *  
+ *     OpenLayers.loadURL(url,params,this,caps);
+ *
+ * Notice the above example does not provide an error handler; a default empty
+ * handler is provided which merely logs the error if a failure handler is not 
+ * supplied
+ *
+ */
+
+
+/**
+ * Function: OpenLayers.nullHandler
+ * @param {} request
+ */
+OpenLayers.nullHandler = function(request) {
+    OpenLayers.Console.userError(OpenLayers.i18n("unhandledRequest", {'statusText':request.statusText}));
+};
+
+/** 
+ * Function: loadURL
+ * Background load a document.  For more flexibility in using XMLHttpRequest,
+ *     see the <OpenLayers.Request> methods.
+ *
+ * Parameters:
+ * uri - {String} URI of source doc
+ * params - {String} or {Object} GET params. Either a string in the form
+ *     "?hello=world&foo=bar" (do not forget the leading question mark)
+ *     or an object in the form {'hello': 'world', 'foo': 'bar}
+ * caller - {Object} object which gets callbacks
+ * onComplete - {Function} Optional callback for success.  The callback
+ *     will be called with this set to caller and will receive the request
+ *     object as an argument.  Note that if you do not specify an onComplete
+ *     function, <OpenLayers.nullHandler> will be called (which pops up a 
+ *     user friendly error message dialog).
+ * onFailure - {Function} Optional callback for failure.  In the event of
+ *     a failure, the callback will be called with this set to caller and will
+ *     receive the request object as an argument.  Note that if you do not
+ *     specify an onComplete function, <OpenLayers.nullHandler> will be called
+ *     (which pops up a user friendly error message dialog).
+ *
+ * Returns:
+ * {<OpenLayers.Request.XMLHttpRequest>}  The request object. To abort loading,
+ *     call request.abort().
+ */
+OpenLayers.loadURL = function(uri, params, caller,
+                                  onComplete, onFailure) {
+    
+    if(typeof params == 'string') {
+        params = OpenLayers.Util.getParameters(params);
+    }
+    var success = (onComplete) ? onComplete : OpenLayers.nullHandler;
+    var failure = (onFailure) ? onFailure : OpenLayers.nullHandler;
+    
+    return OpenLayers.Request.GET({
+        url: uri, params: params,
+        success: success, failure: failure, scope: caller
+    });
+};
+
+/** 
+ * Function: parseXMLString
+ * Parse XML into a doc structure
+ * 
+ * Parameters:
+ * text - {String} 
+ * 
+ * Returns:
+ * {?} Parsed AJAX Responsev
+ */
+OpenLayers.parseXMLString = function(text) {
+
+    //MS sucks, if the server is bad it dies
+    var index = text.indexOf('<');
+    if (index > 0) {
+        text = text.substring(index);
+    }
+
+    var ajaxResponse = OpenLayers.Util.Try(
+        function() {
+            var xmldom = new ActiveXObject('Microsoft.XMLDOM');
+            xmldom.loadXML(text);
+            return xmldom;
+        },
+        function() {
+            return new DOMParser().parseFromString(text, 'text/xml');
+        },
+        function() {
+            var req = new XMLHttpRequest();
+            req.open("GET", "data:" + "text/xml" +
+                     ";charset=utf-8," + encodeURIComponent(text), false);
+            if (req.overrideMimeType) {
+                req.overrideMimeType("text/xml");
+            }
+            req.send(null);
+            return req.responseXML;
+        }
+    );
+
+    return ajaxResponse;
+};
+
+
+/**
+ * Namespace: OpenLayers.Ajax
+ */
+OpenLayers.Ajax = {
+
+    /**
+     * Method: emptyFunction
+     */
+    emptyFunction: function () {},
+
+    /**
+     * Method: getTransport
+     * 
+     * Returns: 
+     * {Object} Transport mechanism for whichever browser we're in, or false if
+     *          none available.
+     */
+    getTransport: function() {
+        return OpenLayers.Util.Try(
+            function() {return new XMLHttpRequest();},
+            function() {return new ActiveXObject('Msxml2.XMLHTTP');},
+            function() {return new ActiveXObject('Microsoft.XMLHTTP');}
+        ) || false;
+    },
+
+    /**
+     * Property: activeRequestCount
+     * {Integer}
+     */
+    activeRequestCount: 0
+};
+
+/**
+ * Namespace: OpenLayers.Ajax.Responders
+ * {Object}
+ */
+OpenLayers.Ajax.Responders = {
+  
+    /**
+     * Property: responders
+     * {Array}
+     */
+    responders: [],
+
+    /**
+     * Method: register
+     *  
+     * Parameters:
+     * responderToAdd - {?}
+     */
+    register: function(responderToAdd) {
+        for (var i = 0; i < this.responders.length; i++){
+            if (responderToAdd == this.responders[i]){
+                return;
+            }
+        }
+        this.responders.push(responderToAdd);
+    },
+
+    /**
+     * Method: unregister
+     *  
+     * Parameters:
+     * responderToRemove - {?}
+     */
+    unregister: function(responderToRemove) {
+        OpenLayers.Util.removeItem(this.reponders, responderToRemove);
+    },
+
+    /**
+     * Method: dispatch
+     * 
+     * Parameters:
+     * callback - {?}
+     * request - {?}
+     * transport - {?}
+     */
+    dispatch: function(callback, request, transport) {
+        var responder;
+        for (var i = 0; i < this.responders.length; i++) {
+            responder = this.responders[i];
+     
+            if (responder[callback] && 
+                typeof responder[callback] == 'function') {
+                try {
+                    responder[callback].apply(responder, 
+                                              [request, transport]);
+                } catch (e) {}
+            }
+        }
+    }
+};
+
+OpenLayers.Ajax.Responders.register({
+    /** 
+     * Function: onCreate
+     */
+    onCreate: function() {
+        OpenLayers.Ajax.activeRequestCount++;
+    },
+
+    /**
+     * Function: onComplete
+     */
+     onComplete: function() {
+         OpenLayers.Ajax.activeRequestCount--;
+     }
+});
+
+/**
+ * Class: OpenLayers.Ajax.Base
+ */
+OpenLayers.Ajax.Base = OpenLayers.Class({
+      
+    /**
+     * Constructor: OpenLayers.Ajax.Base
+     * 
+     * Parameters: 
+     * options - {Object}
+     */
+    initialize: function(options) {
+        this.options = {
+            method:       'post',
+            asynchronous: true,
+            contentType:  'application/xml',
+            parameters:   ''
+        };
+        OpenLayers.Util.extend(this.options, options || {});
+        
+        this.options.method = this.options.method.toLowerCase();
+        
+        if (typeof this.options.parameters == 'string') {
+            this.options.parameters = 
+                OpenLayers.Util.getParameters(this.options.parameters);
+        }
+    }
+});
+
+/**
+ * Class: OpenLayers.Ajax.Request
+ * *Deprecated*.  Use <OpenLayers.Request> method instead.
+ *
+ * Inherit:
+ *  - <OpenLayers.Ajax.Base>
+ */
+OpenLayers.Ajax.Request = OpenLayers.Class(OpenLayers.Ajax.Base, {
+
+    /**
+     * Property: _complete
+     *
+     * {Boolean}
+     */
+    _complete: false,
+      
+    /**
+     * Constructor: OpenLayers.Ajax.Request
+     * 
+     * Parameters: 
+     * url - {String}
+     * options - {Object}
+     */
+    initialize: function(url, options) {
+        OpenLayers.Ajax.Base.prototype.initialize.apply(this, [options]);
+        
+        if (OpenLayers.ProxyHost && OpenLayers.String.startsWith(url, "http")) {
+            url = OpenLayers.ProxyHost + encodeURIComponent(url);
+        }
+        
+        this.transport = OpenLayers.Ajax.getTransport();
+        this.request(url);
+    },
+
+    /**
+     * Method: request
+     * 
+     * Parameters:
+     * url - {String}
+     */
+    request: function(url) {
+        this.url = url;
+        this.method = this.options.method;
+        var params = OpenLayers.Util.extend({}, this.options.parameters);
+        
+        if (this.method != 'get' && this.method != 'post') {
+            // simulate other verbs over post
+            params['_method'] = this.method;
+            this.method = 'post';
+        }
+
+        this.parameters = params;        
+        
+        if (params = OpenLayers.Util.getParameterString(params)) {
+            // when GET, append parameters to URL
+            if (this.method == 'get') {
+                this.url += ((this.url.indexOf('?') > -1) ? '&' : '?') + params;
+            } else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) {
+                params += '&_=';
+            }
+        }
+        try {
+            var response = new OpenLayers.Ajax.Response(this);
+            if (this.options.onCreate) {
+                this.options.onCreate(response);
+            }
+            
+            OpenLayers.Ajax.Responders.dispatch('onCreate', 
+                                                this, 
+                                                response);
+    
+            this.transport.open(this.method.toUpperCase(), 
+                                this.url,
+                                this.options.asynchronous);
+    
+            if (this.options.asynchronous) {
+                window.setTimeout(
+                    OpenLayers.Function.bind(this.respondToReadyState, this, 1),
+                    10);
+            }
+            
+            this.transport.onreadystatechange = 
+                OpenLayers.Function.bind(this.onStateChange, this);    
+            this.setRequestHeaders();
+    
+            this.body =  this.method == 'post' ?
+                (this.options.postBody || params) : null;
+            this.transport.send(this.body);
+    
+            // Force Firefox to handle ready state 4 for synchronous requests
+            if (!this.options.asynchronous && 
+                this.transport.overrideMimeType) {
+                this.onStateChange();
+            }
+        } catch (e) {
+            this.dispatchException(e);
+        }
+    },
+
+    /**
+     * Method: onStateChange
+     */
+    onStateChange: function() {
+        var readyState = this.transport.readyState;
+        if (readyState > 1 && !((readyState == 4) && this._complete)) {
+            this.respondToReadyState(this.transport.readyState);
+        }
+    },
+     
+    /**
+     * Method: setRequestHeaders
+     */
+    setRequestHeaders: function() {
+        var headers = {
+            'X-Requested-With': 'XMLHttpRequest',
+            'Accept': 'text/javascript, text/html, application/xml, text/xml, */*',
+            'OpenLayers': true
+        };
+
+        if (this.method == 'post') {
+            headers['Content-type'] = this.options.contentType +
+                (this.options.encoding ? '; charset=' + this.options.encoding : '');
+    
+            /* Force "Connection: close" for older Mozilla browsers to work
+             * around a bug where XMLHttpRequest sends an incorrect
+             * Content-length header. See Mozilla Bugzilla #246651.
+             */
+            if (this.transport.overrideMimeType &&
+                (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) {
+                headers['Connection'] = 'close';
+            }
+        }
+        // user-defined headers
+        if (typeof this.options.requestHeaders == 'object') {    
+            var extras = this.options.requestHeaders;
+            
+            if (typeof extras.push == 'function') {
+                for (var i = 0, length = extras.length; i < length; i += 2) {
+                    headers[extras[i]] = extras[i+1];
+                }
+            } else {
+                for (var i in extras) {
+                    headers[i] = extras[i];
+                }
+            }
+        }
+        
+        for (var name in headers) {
+            this.transport.setRequestHeader(name, headers[name]);
+        }
+    },
+    
+    /**
+     * Method: success
+     *
+     * Returns:
+     * {Boolean} - 
+     */
+    success: function() {
+        var status = this.getStatus();
+        return !status || (status >=200 && status < 300);
+    },
+    
+    /**
+     * Method: getStatus
+     *
+     * Returns:
+     * {Integer} - Status
+     */
+    getStatus: function() {
+        try {
+            return this.transport.status || 0;
+        } catch (e) {
+            return 0;
+        }
+    },
+
+    /**
+     * Method: respondToReadyState
+     *
+     * Parameters:
+     * readyState - {?}
+     */
+    respondToReadyState: function(readyState) {
+        var state = OpenLayers.Ajax.Request.Events[readyState];
+        var response = new OpenLayers.Ajax.Response(this);
+    
+        if (state == 'Complete') {
+            try {
+                this._complete = true;
+                (this.options['on' + response.status] ||
+                    this.options['on' + (this.success() ? 'Success' : 'Failure')] ||
+                    OpenLayers.Ajax.emptyFunction)(response);
+            } catch (e) {
+                this.dispatchException(e);
+            }
+    
+            var contentType = response.getHeader('Content-type');
+        }
+    
+        try {
+            (this.options['on' + state] || 
+             OpenLayers.Ajax.emptyFunction)(response);
+             OpenLayers.Ajax.Responders.dispatch('on' + state, 
+                                                 this, 
+                                                 response);
+        } catch (e) {
+            this.dispatchException(e);
+        }
+    
+        if (state == 'Complete') {
+            // avoid memory leak in MSIE: clean up
+            this.transport.onreadystatechange = OpenLayers.Ajax.emptyFunction;
+        }
+    },
+    
+    /**
+     * Method: getHeader
+     * 
+     * Parameters:
+     * name - {String} Header name
+     *
+     * Returns:
+     * {?} - response header for the given name
+     */
+    getHeader: function(name) {
+        try {
+            return this.transport.getResponseHeader(name);
+        } catch (e) {
+            return null;
+        }
+    },
+
+    /**
+     * Method: dispatchException
+     * If the optional onException function is set, execute it
+     * and then dispatch the call to any other listener registered
+     * for onException.
+     * 
+     * If no optional onException function is set, we suspect that
+     * the user may have also not used
+     * OpenLayers.Ajax.Responders.register to register a listener
+     * for the onException call.  To make sure that something
+     * gets done with this exception, only dispatch the call if there
+     * are listeners.
+     *
+     * If you explicitly want to swallow exceptions, set
+     * request.options.onException to an empty function (function(){})
+     * or register an empty function with <OpenLayers.Ajax.Responders>
+     * for onException.
+     * 
+     * Parameters:
+     * exception - {?}
+     */
+    dispatchException: function(exception) {
+        var handler = this.options.onException;
+        if(handler) {
+            // call options.onException and alert any other listeners
+            handler(this, exception);
+            OpenLayers.Ajax.Responders.dispatch('onException', this, exception);
+        } else {
+            // check if there are any other listeners
+            var listener = false;
+            var responders = OpenLayers.Ajax.Responders.responders;
+            for (var i = 0; i < responders.length; i++) {
+                if(responders[i].onException) {
+                    listener = true;
+                    break;
+                }
+            }
+            if(listener) {
+                // call all listeners
+                OpenLayers.Ajax.Responders.dispatch('onException', this, exception);
+            } else {
+                // let the exception through
+                throw exception;
+            }
+        }
+    }
+});
+
+/** 
+ * Property: Events
+ * {Array(String)}
+ */
+OpenLayers.Ajax.Request.Events =
+  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
+
+/**
+ * Class: OpenLayers.Ajax.Response
+ */
+OpenLayers.Ajax.Response = OpenLayers.Class({
+
+    /**
+     * Property: status
+     *
+     * {Integer}
+     */
+    status: 0,
+    
+
+    /**
+     * Property: statusText
+     *
+     * {String}
+     */
+    statusText: '',
+      
+    /**
+     * Constructor: OpenLayers.Ajax.Response
+     * 
+     * Parameters: 
+     * request - {Object}
+     */
+    initialize: function(request) {
+        this.request = request;
+        var transport = this.transport = request.transport,
+            readyState = this.readyState = transport.readyState;
+        
+        if ((readyState > 2 &&
+            !(!!(window.attachEvent && !window.opera))) ||
+            readyState == 4) {
+            this.status       = this.getStatus();
+            this.statusText   = this.getStatusText();
+            this.responseText = transport.responseText == null ?
+                '' : String(transport.responseText);
+        }
+        
+        if(readyState == 4) {
+            var xml = transport.responseXML;
+            this.responseXML  = xml === undefined ? null : xml;
+        }
+    },
+    
+    /**
+     * Method: getStatus
+     */
+    getStatus: OpenLayers.Ajax.Request.prototype.getStatus,
+    
+    /**
+     * Method: getStatustext
+     *
+     * Returns:
+     * {String} - statusText
+     */
+    getStatusText: function() {
+        try {
+            return this.transport.statusText || '';
+        } catch (e) {
+            return '';
+        }
+    },
+    
+    /**
+     * Method: getHeader
+     */
+    getHeader: OpenLayers.Ajax.Request.prototype.getHeader,
+    
+    /** 
+     * Method: getResponseHeader
+     *
+     * Returns:
+     * {?} - response header for given name
+     */
+    getResponseHeader: function(name) {
+        return this.transport.getResponseHeader(name);
+    }
+});
+
+
+/**
+ * Function: getElementsByTagNameNS
+ * 
+ * Parameters:
+ * parentnode - {?}
+ * nsuri - {?}
+ * nsprefix - {?}
+ * tagname - {?}
+ * 
+ * Returns:
+ * {?}
+ */
+OpenLayers.Ajax.getElementsByTagNameNS  = function(parentnode, nsuri, 
+                                                   nsprefix, tagname) {
+    var elem = null;
+    if (parentnode.getElementsByTagNameNS) {
+        elem = parentnode.getElementsByTagNameNS(nsuri, tagname);
+    } else {
+        elem = parentnode.getElementsByTagName(nsprefix + ':' + tagname);
+    }
+    return elem;
+};
+
+
+/**
+ * Function: serializeXMLToString
+ * Wrapper function around XMLSerializer, which doesn't exist/work in
+ *     IE/Safari. We need to come up with a way to serialize in those browser:
+ *     for now, these browsers will just fail. #535, #536
+ *
+ * Parameters: 
+ * xmldom {XMLNode} xml dom to serialize
+ * 
+ * Returns:
+ * {?}
+ */
+OpenLayers.Ajax.serializeXMLToString = function(xmldom) {
+    var serializer = new XMLSerializer();
+    var data = serializer.serializeToString(xmldom);
+    return data;
+};
+/* ======================================================================
+    OpenLayers/Control/MouseToolbar.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Control/MouseDefaults.js
+ */
+
+/**
+ * Class: OpenLayers.Control.MouseToolbar
+ * This class is DEPRECATED in 2.4 and will be removed by 3.0.
+ * If you need this functionality, use Control.NavToolbar instead!!! 
+ */
+OpenLayers.Control.MouseToolbar = OpenLayers.Class(
+                                            OpenLayers.Control.MouseDefaults, {
+    
+    /**
+     * Property: mode
+     */ 
+    mode: null,
+    /**
+     * Property: buttons
+     */
+    buttons: null,
+    
+    /**
+     * APIProperty: direction
+     * {String} 'vertical' or 'horizontal'
+     */
+    direction: "vertical",
+    
+    /**
+     * Property: buttonClicked
+     * {String}
+     */
+    buttonClicked: null,
+    
+    /**
+     * Constructor: OpenLayers.Control.MouseToolbar
+     *
+     * Parameters:
+     * position - {<OpenLayers.Pixel>}
+     * direction - {String}
+     */
+    initialize: function(position, direction) {
+        OpenLayers.Control.prototype.initialize.apply(this, arguments);
+        this.position = new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,
+                                             OpenLayers.Control.MouseToolbar.Y);
+        if (position) {
+            this.position = position;
+        }
+        if (direction) {
+            this.direction = direction; 
+        }
+        this.measureDivs = [];
+    },
+    
+    /**
+     * APIMethod: destroy 
+     */
+    destroy: function() {
+        for( var btnId in this.buttons) {
+            var btn = this.buttons[btnId];
+            btn.map = null;
+            btn.events.destroy();
+        }
+        OpenLayers.Control.MouseDefaults.prototype.destroy.apply(this, 
+                                                                 arguments);
+    },
+    
+    /**
+     * Method: draw
+     */
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments); 
+        OpenLayers.Control.MouseDefaults.prototype.draw.apply(this, arguments);
+        this.buttons = {};
+        var sz = new OpenLayers.Size(28,28);
+        var centered = new OpenLayers.Pixel(OpenLayers.Control.MouseToolbar.X,0);
+        this._addButton("zoombox", "drag-rectangle-off.png", "drag-rectangle-on.png", centered, sz, "Shift->Drag to zoom to area");
+        centered = centered.add((this.direction == "vertical" ? 0 : sz.w), (this.direction == "vertical" ? sz.h : 0));
+        this._addButton("pan", "panning-hand-off.png", "panning-hand-on.png", centered, sz, "Drag the map to pan.");
+        centered = centered.add((this.direction == "vertical" ? 0 : sz.w), (this.direction == "vertical" ? sz.h : 0));
+        this.switchModeTo("pan");
+
+        return this.div;
+    },
+    
+    /**
+     * Method: _addButton
+     */
+    _addButton:function(id, img, activeImg, xy, sz, title) {
+        var imgLocation = OpenLayers.Util.getImagesLocation() + img;
+        var activeImgLocation = OpenLayers.Util.getImagesLocation() + activeImg;
+        // var btn = new ol.AlphaImage("_"+id, imgLocation, xy, sz);
+        var btn = OpenLayers.Util.createAlphaImageDiv(
+                                    "OpenLayers_Control_MouseToolbar_" + id, 
+                                    xy, sz, imgLocation, "absolute");
+
+        //we want to add the outer div
+        this.div.appendChild(btn);
+        btn.imgLocation = imgLocation;
+        btn.activeImgLocation = activeImgLocation;
+        
+        btn.events = new OpenLayers.Events(this, btn, null, true);
+        btn.events.on({
+            "mousedown": this.buttonDown,
+            "mouseup": this.buttonUp,
+            "dblclick": OpenLayers.Event.stop,
+            scope: this
+        });
+        btn.action = id;
+        btn.title = title;
+        btn.alt = title;
+        btn.map = this.map;
+
+        //we want to remember/reference the outer div
+        this.buttons[id] = btn;
+        return btn;
+    },
+
+    /**
+     * Method: buttonDown
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    buttonDown: function(evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        this.buttonClicked = evt.element.action;
+        OpenLayers.Event.stop(evt);
+    },
+
+    /**
+     * Method: buttonUp
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    buttonUp: function(evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        if (this.buttonClicked != null) {
+            if (this.buttonClicked == evt.element.action) {
+                this.switchModeTo(evt.element.action);
+            }
+            OpenLayers.Event.stop(evt);
+            this.buttonClicked = null;
+        }
+    },
+    
+    /**
+     * Method: defaultDblClick 
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultDblClick: function (evt) {
+        this.switchModeTo("pan");
+        this.performedDrag = false;
+        var newCenter = this.map.getLonLatFromViewPortPx( evt.xy ); 
+        this.map.setCenter(newCenter, this.map.zoom + 1);
+        OpenLayers.Event.stop(evt);
+        return false;
+    },
+
+    /**
+     * Method: defaultMouseDown
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseDown: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        this.mouseDragStart = evt.xy.clone();
+        this.performedDrag = false;
+        this.startViaKeyboard = false;
+        if (evt.shiftKey && this.mode !="zoombox") {
+            this.switchModeTo("zoombox");
+            this.startViaKeyboard = true;
+        } else if (evt.altKey && this.mode !="measure") {
+            this.switchModeTo("measure");
+        } else if (!this.mode) {
+            this.switchModeTo("pan");
+        }
+        
+        switch (this.mode) {
+            case "zoombox":
+                this.map.div.style.cursor = "crosshair";
+                this.zoomBox = OpenLayers.Util.createDiv('zoomBox',
+                                                         this.mouseDragStart,
+                                                         null,
+                                                         null,
+                                                         "absolute",
+                                                         "2px solid red");
+                this.zoomBox.style.backgroundColor = "white";
+                this.zoomBox.style.filter = "alpha(opacity=50)"; // IE
+                this.zoomBox.style.opacity = "0.50";
+                this.zoomBox.style.fontSize = "1px";
+                this.zoomBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+                this.map.viewPortDiv.appendChild(this.zoomBox);
+                this.performedDrag = true;
+                break;
+            case "measure":
+                var distance = "";
+                if (this.measureStart) {
+                    var measureEnd = this.map.getLonLatFromViewPortPx(this.mouseDragStart);
+                    distance = OpenLayers.Util.distVincenty(this.measureStart, measureEnd);
+                    distance = Math.round(distance * 100) / 100;
+                    distance = distance + "km";
+                    this.measureStartBox = this.measureBox;
+                }    
+                this.measureStart = this.map.getLonLatFromViewPortPx(this.mouseDragStart);;
+                this.measureBox = OpenLayers.Util.createDiv(null,
+                                                         this.mouseDragStart.add(
+                                                           -2-parseInt(this.map.layerContainerDiv.style.left),
+                                                           -2-parseInt(this.map.layerContainerDiv.style.top)),
+                                                         null,
+                                                         null,
+                                                         "absolute");
+                this.measureBox.style.width="4px";
+                this.measureBox.style.height="4px";
+                this.measureBox.style.fontSize = "1px";
+                this.measureBox.style.backgroundColor="red";
+                this.measureBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+                this.map.layerContainerDiv.appendChild(this.measureBox);
+                if (distance) {
+                    this.measureBoxDistance = OpenLayers.Util.createDiv(null,
+                                                         this.mouseDragStart.add(
+                                                           -2-parseInt(this.map.layerContainerDiv.style.left),
+                                                           2-parseInt(this.map.layerContainerDiv.style.top)),
+                                                         null,
+                                                         null,
+                                                         "absolute");
+                    
+                    this.measureBoxDistance.innerHTML = distance;
+                    this.measureBoxDistance.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+                    this.map.layerContainerDiv.appendChild(this.measureBoxDistance);
+                    this.measureDivs.push(this.measureBoxDistance);
+                }
+                this.measureBox.style.zIndex = this.map.Z_INDEX_BASE["Popup"] - 1;
+                this.map.layerContainerDiv.appendChild(this.measureBox);
+                this.measureDivs.push(this.measureBox);
+                break;
+            default:
+                this.map.div.style.cursor = "move";
+                break;
+        }
+        document.onselectstart = function() { return false; };
+        OpenLayers.Event.stop(evt);
+    },
+
+    /**
+     * Method: switchModeTo 
+     *
+     * Parameters:
+     * mode - {String} 
+     */
+    switchModeTo: function(mode) {
+        if (mode != this.mode) {
+            
+
+            if (this.mode && this.buttons[this.mode]) {
+                OpenLayers.Util.modifyAlphaImageDiv(this.buttons[this.mode], null, null, null, this.buttons[this.mode].imgLocation);
+            }
+            if (this.mode == "measure" && mode != "measure") {
+                for(var i=0, len=this.measureDivs.length; i<len; i++) {
+                    if (this.measureDivs[i]) { 
+                        this.map.layerContainerDiv.removeChild(this.measureDivs[i]);
+                    }
+                }
+                this.measureDivs = [];
+                this.measureStart = null;
+            }
+            this.mode = mode;
+            if (this.buttons[mode]) {
+                OpenLayers.Util.modifyAlphaImageDiv(this.buttons[mode], null, null, null, this.buttons[mode].activeImgLocation);
+            }
+            switch (this.mode) {
+                case "zoombox":
+                    this.map.div.style.cursor = "crosshair";
+                    break;
+                default:
+                    this.map.div.style.cursor = "";
+                    break;
+            }
+
+        } 
+    }, 
+
+    /**
+     * Method: leaveMode
+     */
+    leaveMode: function() {
+        this.switchModeTo("pan");
+    },
+    
+    /**
+     * Method: defaultMouseMove
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseMove: function (evt) {
+        if (this.mouseDragStart != null) {
+            switch (this.mode) {
+                case "zoombox": 
+                    var deltaX = Math.abs(this.mouseDragStart.x - evt.xy.x);
+                    var deltaY = Math.abs(this.mouseDragStart.y - evt.xy.y);
+                    this.zoomBox.style.width = Math.max(1, deltaX) + "px";
+                    this.zoomBox.style.height = Math.max(1, deltaY) + "px";
+                    if (evt.xy.x < this.mouseDragStart.x) {
+                        this.zoomBox.style.left = evt.xy.x+"px";
+                    }
+                    if (evt.xy.y < this.mouseDragStart.y) {
+                        this.zoomBox.style.top = evt.xy.y+"px";
+                    }
+                    break;
+                default:
+                    var deltaX = this.mouseDragStart.x - evt.xy.x;
+                    var deltaY = this.mouseDragStart.y - evt.xy.y;
+                    var size = this.map.getSize();
+                    var newXY = new OpenLayers.Pixel(size.w / 2 + deltaX,
+                                                     size.h / 2 + deltaY);
+                    var newCenter = this.map.getLonLatFromViewPortPx( newXY ); 
+                    this.map.setCenter(newCenter, null, true);
+                    this.mouseDragStart = evt.xy.clone();
+            }
+            this.performedDrag = true;
+        }
+    },
+
+    /**
+     * Method: defaultMouseUp
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseUp: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        switch (this.mode) {
+            case "zoombox":
+                this.zoomBoxEnd(evt);
+                if (this.startViaKeyboard) {
+                    this.leaveMode();
+                }
+                break;
+            case "pan":
+                if (this.performedDrag) {
+                    this.map.setCenter(this.map.center);
+                }        
+        }
+        document.onselectstart = null;
+        this.mouseDragStart = null;
+        this.map.div.style.cursor = "default";
+    },
+
+    /**
+     * Method: defaultMouseOut
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultMouseOut: function (evt) {
+        if (this.mouseDragStart != null
+            && OpenLayers.Util.mouseLeft(evt, this.map.div)) {
+            if (this.zoomBox) {
+                this.removeZoomBox();
+                if (this.startViaKeyboard) {
+                    this.leaveMode();
+                }
+            }
+            this.mouseDragStart = null;
+            this.map.div.style.cursor = "default";
+        }
+    },
+
+    /**
+     * Method: defaultClick
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultClick: function (evt) {
+        if (this.performedDrag)  {
+            this.performedDrag = false;
+            return false;
+        }
+    },
+    
+    CLASS_NAME: "OpenLayers.Control.MouseToolbar"
+});
+
+OpenLayers.Control.MouseToolbar.X = 6;
+OpenLayers.Control.MouseToolbar.Y = 300;
+/* ======================================================================
+    OpenLayers/Control/PanZoomBar.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control/PanZoom.js
+ */
+
+/**
+ * Class: OpenLayers.Control.PanZoomBar
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control.PanZoom>
+ */
+OpenLayers.Control.PanZoomBar = OpenLayers.Class(OpenLayers.Control.PanZoom, {
+
+    /** 
+     * APIProperty: zoomStopWidth
+     */
+    zoomStopWidth: 18,
+
+    /** 
+     * APIProperty: zoomStopHeight
+     */
+    zoomStopHeight: 11,
+
+    /** 
+     * Property: slider
+     */
+    slider: null,
+
+    /** 
+     * Property: sliderEvents
+     * {<OpenLayers.Events>}
+     */
+    sliderEvents: null,
+
+    /** 
+     * Property: zoomBarDiv
+     * {DOMElement}
+     */
+    zoomBarDiv: null,
+
+    /** 
+     * Property: divEvents
+     * {<OpenLayers.Events>}
+     */
+    divEvents: null,
+
+    /** 
+     * Property: zoomWorldIcon
+     * {Boolean}
+     */
+    zoomWorldIcon: false,
+
+    /**
+     * Constructor: OpenLayers.Control.PanZoomBar
+     */ 
+    initialize: function() {
+        OpenLayers.Control.PanZoom.prototype.initialize.apply(this, arguments);
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+
+        this.div.removeChild(this.slider);
+        this.slider = null;
+
+        this.sliderEvents.destroy();
+        this.sliderEvents = null;
+        
+        this.div.removeChild(this.zoombarDiv);
+        this.zoomBarDiv = null;
+
+        this.divEvents.destroy();
+        this.divEvents = null;
+
+        this.map.events.un({
+            "zoomend": this.moveZoomBar,
+            "changebaselayer": this.redraw,
+            scope: this
+        });
+
+        OpenLayers.Control.PanZoom.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * Method: setMap
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        OpenLayers.Control.PanZoom.prototype.setMap.apply(this, arguments);
+        this.map.events.register("changebaselayer", this, this.redraw);
+    },
+
+    /** 
+     * Method: redraw
+     * clear the div and start over.
+     */
+    redraw: function() {
+        if (this.div != null) {
+            this.div.innerHTML = "";
+        }  
+        this.draw();
+    },
+    
+    /**
+    * Method: draw 
+    *
+    * Parameters:
+    * px - {<OpenLayers.Pixel>} 
+    */
+    draw: function(px) {
+        // initialize our internal div
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+        px = this.position.clone();
+
+        // place the controls
+        this.buttons = [];
+
+        var sz = new OpenLayers.Size(18,18);
+        var centered = new OpenLayers.Pixel(px.x+sz.w/2, px.y);
+        var wposition = sz.w;
+
+        if (this.zoomWorldIcon) {
+            centered = new OpenLayers.Pixel(px.x+sz.w, px.y);
+        }
+
+        this._addButton("panup", "north-mini.png", centered, sz);
+        px.y = centered.y+sz.h;
+        this._addButton("panleft", "west-mini.png", px, sz);
+        if (this.zoomWorldIcon) {
+            this._addButton("zoomworld", "zoom-world-mini.png", px.add(sz.w, 0), sz);
+            
+            wposition *= 2;
+        }
+        this._addButton("panright", "east-mini.png", px.add(wposition, 0), sz);
+        this._addButton("pandown", "south-mini.png", centered.add(0, sz.h*2), sz);
+        this._addButton("zoomin", "zoom-plus-mini.png", centered.add(0, sz.h*3+5), sz);
+        centered = this._addZoomBar(centered.add(0, sz.h*4 + 5));
+        this._addButton("zoomout", "zoom-minus-mini.png", centered, sz);
+        return this.div;
+    },
+
+    /** 
+    * Method: _addZoomBar
+    * 
+    * Parameters:
+    * location - {<OpenLayers.Pixel>} where zoombar drawing is to start.
+    */
+    _addZoomBar:function(centered) {
+        var imgLocation = OpenLayers.Util.getImagesLocation();
+        
+        var id = this.id + "_" + this.map.id;
+        var zoomsToEnd = this.map.getNumZoomLevels() - 1 - this.map.getZoom();
+        var slider = OpenLayers.Util.createAlphaImageDiv(id,
+                       centered.add(-1, zoomsToEnd * this.zoomStopHeight), 
+                       new OpenLayers.Size(20,9), 
+                       imgLocation+"slider.png",
+                       "absolute");
+        this.slider = slider;
+        
+        this.sliderEvents = new OpenLayers.Events(this, slider, null, true,
+                                            {includeXY: true});
+        this.sliderEvents.on({
+            "mousedown": this.zoomBarDown,
+            "mousemove": this.zoomBarDrag,
+            "mouseup": this.zoomBarUp,
+            "dblclick": this.doubleClick,
+            "click": this.doubleClick
+        });
+        
+        var sz = new OpenLayers.Size();
+        sz.h = this.zoomStopHeight * this.map.getNumZoomLevels();
+        sz.w = this.zoomStopWidth;
+        var div = null;
+        
+        if (OpenLayers.Util.alphaHack()) {
+            var id = this.id + "_" + this.map.id;
+            div = OpenLayers.Util.createAlphaImageDiv(id, centered,
+                                      new OpenLayers.Size(sz.w, 
+                                              this.zoomStopHeight),
+                                      imgLocation + "zoombar.png", 
+                                      "absolute", null, "crop");
+            div.style.height = sz.h + "px";
+        } else {
+            div = OpenLayers.Util.createDiv(
+                        'OpenLayers_Control_PanZoomBar_Zoombar' + this.map.id,
+                        centered,
+                        sz,
+                        imgLocation+"zoombar.png");
+        }
+        
+        this.zoombarDiv = div;
+        
+        this.divEvents = new OpenLayers.Events(this, div, null, true, 
+                                                {includeXY: true});
+        this.divEvents.on({
+            "mousedown": this.divClick,
+            "mousemove": this.passEventToSlider,
+            "dblclick": this.doubleClick,
+            "click": this.doubleClick
+        });
+        
+        this.div.appendChild(div);
+
+        this.startTop = parseInt(div.style.top);
+        this.div.appendChild(slider);
+
+        this.map.events.register("zoomend", this, this.moveZoomBar);
+
+        centered = centered.add(0, 
+            this.zoomStopHeight * this.map.getNumZoomLevels());
+        return centered; 
+    },
+    
+    /*
+     * Method: passEventToSlider
+     * This function is used to pass events that happen on the div, or the map,
+     * through to the slider, which then does its moving thing.
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     */
+    passEventToSlider:function(evt) {
+        this.sliderEvents.handleBrowserEvent(evt);
+    },
+    
+    /*
+     * Method: divClick
+     * Picks up on clicks directly on the zoombar div
+     *           and sets the zoom level appropriately.
+     */
+    divClick: function (evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        var y = evt.xy.y;
+        var top = OpenLayers.Util.pagePosition(evt.object)[1];
+        var levels = (y - top)/this.zoomStopHeight;
+        if(!this.map.fractionalZoom) {
+            levels = Math.floor(levels);
+        }    
+        var zoom = (this.map.getNumZoomLevels() - 1) - levels; 
+        zoom = Math.min(Math.max(zoom, 0), this.map.getNumZoomLevels() - 1);
+        this.map.zoomTo(zoom);
+        OpenLayers.Event.stop(evt);
+    },
+    
+    /*
+     * Method: zoomBarDown
+     * event listener for clicks on the slider
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     */
+    zoomBarDown:function(evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        this.map.events.on({
+            "mousemove": this.passEventToSlider,
+            "mouseup": this.passEventToSlider,
+            scope: this
+        });
+        this.mouseDragStart = evt.xy.clone();
+        this.zoomStart = evt.xy.clone();
+        this.div.style.cursor = "move";
+        // reset the div offsets just in case the div moved
+        this.zoombarDiv.offsets = null; 
+        OpenLayers.Event.stop(evt);
+    },
+    
+    /*
+     * Method: zoomBarDrag
+     * This is what happens when a click has occurred, and the client is
+     * dragging.  Here we must ensure that the slider doesn't go beyond the
+     * bottom/top of the zoombar div, as well as moving the slider to its new
+     * visual location
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     */
+    zoomBarDrag:function(evt) {
+        if (this.mouseDragStart != null) {
+            var deltaY = this.mouseDragStart.y - evt.xy.y;
+            var offsets = OpenLayers.Util.pagePosition(this.zoombarDiv);
+            if ((evt.clientY - offsets[1]) > 0 && 
+                (evt.clientY - offsets[1]) < parseInt(this.zoombarDiv.style.height) - 2) {
+                var newTop = parseInt(this.slider.style.top) - deltaY;
+                this.slider.style.top = newTop+"px";
+            }
+            this.mouseDragStart = evt.xy.clone();
+            OpenLayers.Event.stop(evt);
+        }
+    },
+    
+    /*
+     * Method: zoomBarUp
+     * Perform cleanup when a mouseup event is received -- discover new zoom
+     * level and switch to it.
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>} 
+     */
+    zoomBarUp:function(evt) {
+        if (!OpenLayers.Event.isLeftClick(evt)) {
+            return;
+        }
+        if (this.zoomStart) {
+            this.div.style.cursor="";
+            this.map.events.un({
+                "mouseup": this.passEventToSlider,
+                "mousemove": this.passEventToSlider,
+                scope: this
+            });
+            var deltaY = this.zoomStart.y - evt.xy.y;
+            var zoomLevel = this.map.zoom;
+            if (this.map.fractionalZoom) {
+                zoomLevel += deltaY/this.zoomStopHeight;
+                zoomLevel = Math.min(Math.max(zoomLevel, 0), 
+                                     this.map.getNumZoomLevels() - 1);
+            } else {
+                zoomLevel += Math.round(deltaY/this.zoomStopHeight);
+            }
+            this.map.zoomTo(zoomLevel);
+            this.moveZoomBar();
+            this.mouseDragStart = null;
+            OpenLayers.Event.stop(evt);
+        }
+    },
+    
+    /*
+    * Method: moveZoomBar
+    * Change the location of the slider to match the current zoom level.
+    */
+    moveZoomBar:function() {
+        var newTop = 
+            ((this.map.getNumZoomLevels()-1) - this.map.getZoom()) * 
+            this.zoomStopHeight + this.startTop + 1;
+        this.slider.style.top = newTop + "px";
+    },    
+    
+    CLASS_NAME: "OpenLayers.Control.PanZoomBar"
+});
+/* ======================================================================
+    OpenLayers/Control/Permalink.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Control/ArgParser.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Permalink
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.Permalink = OpenLayers.Class(OpenLayers.Control, {
+    
+    /**
+     * APIProperty: argParserClass
+     * {Class} The ArgParser control class (not instance) to use with this
+     *     control.
+     */
+    argParserClass: OpenLayers.Control.ArgParser,
+
+    /** 
+     * Property: element 
+     * {DOMElement}
+     */
+    element: null,
+    
+    /** 
+     * APIProperty: base
+     * {String}
+     */
+    base: '',
+
+    /** 
+     * APIProperty: displayProjection
+     * {<OpenLayers.Projection>} Requires proj4js support.  Projection used
+     *     when creating the coordinates in the link. This will reproject the
+     *     map coordinates into display coordinates. If you are using this
+     *     functionality, the permalink which is last added to the map will
+     *     determine the coordinate type which is read from the URL, which
+     *     means you should not add permalinks with different
+     *     displayProjections to the same map. 
+     */
+    displayProjection: null, 
+
+    /**
+     * Constructor: OpenLayers.Control.Permalink
+     *
+     * Parameters: 
+     * element - {DOMElement} 
+     * base - {String} 
+     * options - {Object} options to the control. 
+     */
+    initialize: function(element, base, options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.element = OpenLayers.Util.getElement(element);        
+        this.base = base || document.location.href;
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function()  {
+        if (this.element.parentNode == this.div) {
+            this.div.removeChild(this.element);
+        }
+        this.element = null;
+
+        this.map.events.unregister('moveend', this, this.updateLink);
+
+        OpenLayers.Control.prototype.destroy.apply(this, arguments); 
+    },
+
+    /**
+     * Method: setMap
+     * Set the map property for the control. 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        OpenLayers.Control.prototype.setMap.apply(this, arguments);
+
+        //make sure we have an arg parser attached
+        for(var i=0, len=this.map.controls.length; i<len; i++) {
+            var control = this.map.controls[i];
+            if (control.CLASS_NAME == this.argParserClass.CLASS_NAME) {
+                
+                // If a permalink is added to the map, and an ArgParser already
+                // exists, we override the displayProjection to be the one
+                // on the permalink. 
+                if (control.displayProjection != this.displayProjection) {
+                    this.displayProjection = control.displayProjection;
+                }    
+                
+                break;
+            }
+        }
+        if (i == this.map.controls.length) {
+            this.map.addControl(new this.argParserClass(
+                { 'displayProjection': this.displayProjection }));       
+        }
+
+    },
+
+    /**
+     * Method: draw
+     *
+     * Returns:
+     * {DOMElement}
+     */    
+    draw: function() {
+        OpenLayers.Control.prototype.draw.apply(this, arguments);
+          
+        if (!this.element) {
+            this.div.className = this.displayClass;
+            this.element = document.createElement("a");
+            this.element.innerHTML = OpenLayers.i18n("permalink");
+            this.element.href="";
+            this.div.appendChild(this.element);
+        }
+        this.map.events.on({
+            'moveend': this.updateLink,
+            'changelayer': this.updateLink,
+            'changebaselayer': this.updateLink,
+            scope: this
+        });
+        
+        // Make it so there is at least a link even though the map may not have
+        // moved yet.
+        this.updateLink();
+        
+        return this.div;
+    },
+   
+    /**
+     * Method: updateLink 
+     */
+    updateLink: function() {
+        var href = this.base;
+        if (href.indexOf('?') != -1) {
+            href = href.substring( 0, href.indexOf('?') );
+        }
+
+        href += '?' + OpenLayers.Util.getParameterString(this.createParams());
+        this.element.href = href;
+    }, 
+    
+    /**
+     * APIMethod: createParams
+     * Creates the parameters that need to be encoded into the permalink url.
+     * 
+     * Parameters:
+     * center - {<OpenLayers.LonLat>} center to encode in the permalink.
+     *     Defaults to the current map center.
+     * zoom - {Integer} zoom level to encode in the permalink. Defaults to the
+     *     current map zoom level.
+     * layers - {Array(<OpenLayers.Layer>)} layers to encode in the permalink.
+     *     Defaults to the current map layers.
+     * 
+     * Returns:
+     * {Object} Hash of parameters that will be url-encoded into the
+     * permalink.
+     */
+    createParams: function(center, zoom, layers) {
+        center = center || this.map.getCenter();
+        zoom = zoom || this.map.getZoom();
+        layers = layers || this.map.layers;  
+          
+        var params = OpenLayers.Util.getParameters(this.base);
+        
+        // If there's still no center, map is not initialized yet. 
+        // Break out of this function, and simply return the params from the
+        // base link.
+        if (center) { 
+    
+            params.zoom = this.map.getZoom(); 
+            var lat = center.lat;
+            var lon = center.lon;
+            
+            if (this.displayProjection) {
+                var mapPosition = OpenLayers.Projection.transform(
+                  { x: lon, y: lat }, 
+                  this.map.getProjectionObject(), 
+                  this.displayProjection );
+                lon = mapPosition.x;  
+                lat = mapPosition.y;  
+            }       
+            params.lat = Math.round(lat*100000)/100000;
+            params.lon = Math.round(lon*100000)/100000;
+            
+            params.layers = '';
+            for (var i=0, len=this.map.layers.length; i<len; i++) {
+                var layer = this.map.layers[i];
+    
+                if (layer.isBaseLayer) {
+                    params.layers += (layer == this.map.baseLayer) ? "B" : "0";
+                } else {
+                    params.layers += (layer.getVisibility()) ? "T" : "F";           
+                }
+            }
+        }
+
+        return params;
+    }, 
+
+    CLASS_NAME: "OpenLayers.Control.Permalink"
+});
+/* ======================================================================
+    OpenLayers/Format/JSON.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * Note:
+ * This work draws heavily from the public domain JSON serializer/deserializer
+ *     at http://www.json.org/json.js. Rewritten so that it doesn't modify
+ *     basic data prototypes.
+ */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.JSON
+ * A parser to read/write JSON safely.  Create a new instance with the
+ *     <OpenLayers.Format.JSON> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.JSON = OpenLayers.Class(OpenLayers.Format, {
+    
+    /**
+     * APIProperty: indent
+     * {String} For "pretty" printing, the indent string will be used once for
+     *     each indentation level.
+     */
+    indent: "    ",
+    
+    /**
+     * APIProperty: space
+     * {String} For "pretty" printing, the space string will be used after
+     *     the ":" separating a name/value pair.
+     */
+    space: " ",
+    
+    /**
+     * APIProperty: newline
+     * {String} For "pretty" printing, the newline string will be used at the
+     *     end of each name/value pair or array item.
+     */
+    newline: "\n",
+    
+    /**
+     * Property: level
+     * {Integer} For "pretty" printing, this is incremented/decremented during
+     *     serialization.
+     */
+    level: 0,
+
+    /**
+     * Property: pretty
+     * {Boolean} Serialize with extra whitespace for structure.  This is set
+     *     by the <write> method.
+     */
+    pretty: false,
+
+    /**
+     * Constructor: OpenLayers.Format.JSON
+     * Create a new parser for JSON.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: read
+     * Deserialize a json string.
+     *
+     * Parameters:
+     * json - {String} A JSON string
+     * filter - {Function} A function which will be called for every key and
+     *     value at every level of the final result. Each value will be
+     *     replaced by the result of the filter function. This can be used to
+     *     reform generic objects into instances of classes, or to transform
+     *     date strings into Date objects.
+     *     
+     * Returns:
+     * {Object} An object, array, string, or number .
+     */
+    read: function(json, filter) {
+        /**
+         * Parsing happens in three stages. In the first stage, we run the text
+         *     against a regular expression which looks for non-JSON
+         *     characters. We are especially concerned with '()' and 'new'
+         *     because they can cause invocation, and '=' because it can cause
+         *     mutation. But just to be safe, we will reject all unexpected
+         *     characters.
+         */
+        try {
+            if (/^[\],:{}\s]*$/.test(json.replace(/\\["\\\/bfnrtu]/g, '@').
+                                replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+                                replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+                /**
+                 * In the second stage we use the eval function to compile the
+                 *     text into a JavaScript structure. The '{' operator is
+                 *     subject to a syntactic ambiguity in JavaScript - it can
+                 *     begin a block or an object literal. We wrap the text in
+                 *     parens to eliminate the ambiguity.
+                 */
+                var object = eval('(' + json + ')');
+
+                /**
+                 * In the optional third stage, we recursively walk the new
+                 *     structure, passing each name/value pair to a filter
+                 *     function for possible transformation.
+                 */
+                if(typeof filter === 'function') {
+                    function walk(k, v) {
+                        if(v && typeof v === 'object') {
+                            for(var i in v) {
+                                if(v.hasOwnProperty(i)) {
+                                    v[i] = walk(i, v[i]);
+                                }
+                            }
+                        }
+                        return filter(k, v);
+                    }
+                    object = walk('', object);
+                }
+                return object;
+            }
+        } catch(e) {
+            // Fall through if the regexp test fails.
+        }
+        return null;
+    },
+
+    /**
+     * APIMethod: write
+     * Serialize an object into a JSON string.
+     *
+     * Parameters:
+     * value - {String} The object, array, string, number, boolean or date
+     *     to be serialized.
+     * pretty - {Boolean} Structure the output with newlines and indentation.
+     *     Default is false.
+     *
+     * Returns:
+     * {String} The JSON string representation of the input value.
+     */
+    write: function(value, pretty) {
+        this.pretty = !!pretty;
+        var json = null;
+        var type = typeof value;
+        if(this.serialize[type]) {
+            json = this.serialize[type].apply(this, [value]);
+        }
+        return json;
+    },
+    
+    /**
+     * Method: writeIndent
+     * Output an indentation string depending on the indentation level.
+     *
+     * Returns:
+     * {String} An appropriate indentation string.
+     */
+    writeIndent: function() {
+        var pieces = [];
+        if(this.pretty) {
+            for(var i=0; i<this.level; ++i) {
+                pieces.push(this.indent);
+            }
+        }
+        return pieces.join('');
+    },
+    
+    /**
+     * Method: writeNewline
+     * Output a string representing a newline if in pretty printing mode.
+     *
+     * Returns:
+     * {String} A string representing a new line.
+     */
+    writeNewline: function() {
+        return (this.pretty) ? this.newline : '';
+    },
+    
+    /**
+     * Method: writeSpace
+     * Output a string representing a space if in pretty printing mode.
+     *
+     * Returns:
+     * {String} A space.
+     */
+    writeSpace: function() {
+        return (this.pretty) ? this.space : '';
+    },
+
+    /**
+     * Property: serialize
+     * Object with properties corresponding to the serializable data types.
+     *     Property values are functions that do the actual serializing.
+     */
+    serialize: {
+        /**
+         * Method: serialize.object
+         * Transform an object into a JSON string.
+         *
+         * Parameters:
+         * object - {Object} The object to be serialized.
+         * 
+         * Returns:
+         * {String} A JSON string representing the object.
+         */
+        'object': function(object) {
+            // three special objects that we want to treat differently
+            if(object == null) {
+                return "null";
+            }
+            if(object.constructor == Date) {
+                return this.serialize.date.apply(this, [object]);
+            }
+            if(object.constructor == Array) {
+                return this.serialize.array.apply(this, [object]);
+            }
+            var pieces = ['{'];
+            this.level += 1;
+            var key, keyJSON, valueJSON;
+            
+            var addComma = false;
+            for(key in object) {
+                if(object.hasOwnProperty(key)) {
+                    // recursive calls need to allow for sub-classing
+                    keyJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+                                                    [key, this.pretty]);
+                    valueJSON = OpenLayers.Format.JSON.prototype.write.apply(this,
+                                                    [object[key], this.pretty]);
+                    if(keyJSON != null && valueJSON != null) {
+                        if(addComma) {
+                            pieces.push(',');
+                        }
+                        pieces.push(this.writeNewline(), this.writeIndent(),
+                                    keyJSON, ':', this.writeSpace(), valueJSON);
+                        addComma = true;
+                    }
+                }
+            }
+            
+            this.level -= 1;
+            pieces.push(this.writeNewline(), this.writeIndent(), '}');
+            return pieces.join('');
+        },
+        
+        /**
+         * Method: serialize.array
+         * Transform an array into a JSON string.
+         *
+         * Parameters:
+         * array - {Array} The array to be serialized
+         * 
+         * Returns:
+         * {String} A JSON string representing the array.
+         */
+        'array': function(array) {
+            var json;
+            var pieces = ['['];
+            this.level += 1;
+    
+            for(var i=0, len=array.length; i<len; ++i) {
+                // recursive calls need to allow for sub-classing
+                json = OpenLayers.Format.JSON.prototype.write.apply(this,
+                                                    [array[i], this.pretty]);
+                if(json != null) {
+                    if(i > 0) {
+                        pieces.push(',');
+                    }
+                    pieces.push(this.writeNewline(), this.writeIndent(), json);
+                }
+            }
+
+            this.level -= 1;    
+            pieces.push(this.writeNewline(), this.writeIndent(), ']');
+            return pieces.join('');
+        },
+        
+        /**
+         * Method: serialize.string
+         * Transform a string into a JSON string.
+         *
+         * Parameters:
+         * string - {String} The string to be serialized
+         * 
+         * Returns:
+         * {String} A JSON string representing the string.
+         */
+        'string': function(string) {
+            // If the string contains no control characters, no quote characters, and no
+            // backslash characters, then we can simply slap some quotes around it.
+            // Otherwise we must also replace the offending characters with safe
+            // sequences.    
+            var m = {
+                '\b': '\\b',
+                '\t': '\\t',
+                '\n': '\\n',
+                '\f': '\\f',
+                '\r': '\\r',
+                '"' : '\\"',
+                '\\': '\\\\'
+            };
+            if(/["\\\x00-\x1f]/.test(string)) {
+                return '"' + string.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+                    var c = m[b];
+                    if(c) {
+                        return c;
+                    }
+                    c = b.charCodeAt();
+                    return '\\u00' +
+                        Math.floor(c / 16).toString(16) +
+                        (c % 16).toString(16);
+                }) + '"';
+            }
+            return '"' + string + '"';
+        },
+
+        /**
+         * Method: serialize.number
+         * Transform a number into a JSON string.
+         *
+         * Parameters:
+         * number - {Number} The number to be serialized.
+         *
+         * Returns:
+         * {String} A JSON string representing the number.
+         */
+        'number': function(number) {
+            return isFinite(number) ? String(number) : "null";
+        },
+        
+        /**
+         * Method: serialize.boolean
+         * Transform a boolean into a JSON string.
+         *
+         * Parameters:
+         * bool - {Boolean} The boolean to be serialized.
+         * 
+         * Returns:
+         * {String} A JSON string representing the boolean.
+         */
+        'boolean': function(bool) {
+            return String(bool);
+        },
+        
+        /**
+         * Method: serialize.object
+         * Transform a date into a JSON string.
+         *
+         * Parameters:
+         * date - {Date} The date to be serialized.
+         * 
+         * Returns:
+         * {String} A JSON string representing the date.
+         */
+        'date': function(date) {    
+            function format(number) {
+                // Format integers to have at least two digits.
+                return (number < 10) ? '0' + number : number;
+            }
+            return '"' + date.getFullYear() + '-' +
+                    format(date.getMonth() + 1) + '-' +
+                    format(date.getDate()) + 'T' +
+                    format(date.getHours()) + ':' +
+                    format(date.getMinutes()) + ':' +
+                    format(date.getSeconds()) + '"';
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Format.JSON" 
+
+});     
+/* ======================================================================
+    OpenLayers/Format/XML.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ */
+
+/**
+ * Class: OpenLayers.Format.XML
+ * Read and write XML.  For cross-browser XML generation, use methods on an
+ *     instance of the XML format class instead of on <code>document<end>.
+ *     The DOM creation and traversing methods exposed here all mimic the
+ *     W3C XML DOM methods.  Create a new parser with the
+ *     <OpenLayers.Format.XML> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.XML = OpenLayers.Class(OpenLayers.Format, {
+    
+    /**
+     * Property: xmldom
+     * {XMLDom} If this browser uses ActiveX, this will be set to a XMLDOM
+     *     object.  It is not intended to be a browser sniffing property.
+     *     Instead, the xmldom property is used instead of <code>document<end>
+     *     where namespaced node creation methods are not supported. In all
+     *     other browsers, this remains null.
+     */
+    xmldom: null,
+
+    /**
+     * Constructor: OpenLayers.Format.XML
+     * Construct an XML parser.  The parser is used to read and write XML.
+     *     Reading XML from a string returns a DOM element.  Writing XML from
+     *     a DOM element returns a string.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on
+     *     the object.
+     */
+    initialize: function(options) {
+        if(window.ActiveXObject) {
+            this.xmldom = new ActiveXObject("Microsoft.XMLDOM");
+        }
+        OpenLayers.Format.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: read
+     * Deserialize a XML string and return a DOM node.
+     *
+     * Parameters:
+     * text - {String} A XML string
+     
+     * Returns:
+     * {DOMElement} A DOM node
+     */
+    read: function(text) {
+        var index = text.indexOf('<');
+        if(index > 0) {
+            text = text.substring(index);
+        }
+        var node = OpenLayers.Util.Try(
+            OpenLayers.Function.bind((
+                function() {
+                    var xmldom;
+                    /**
+                     * Since we want to be able to call this method on the prototype
+                     * itself, this.xmldom may not exist even if in IE.
+                     */
+                    if(window.ActiveXObject && !this.xmldom) {
+                        xmldom = new ActiveXObject("Microsoft.XMLDOM");
+                    } else {
+                        xmldom = this.xmldom;
+                        
+                    }
+                    xmldom.loadXML(text);
+                    return xmldom;
+                }
+            ), this),
+            function() {
+                return new DOMParser().parseFromString(text, 'text/xml');
+            },
+            function() {
+                var req = new XMLHttpRequest();
+                req.open("GET", "data:" + "text/xml" +
+                         ";charset=utf-8," + encodeURIComponent(text), false);
+                if(req.overrideMimeType) {
+                    req.overrideMimeType("text/xml");
+                }
+                req.send(null);
+                return req.responseXML;
+            }
+        );
+        return node;
+    },
+
+    /**
+     * APIMethod: write
+     * Serialize a DOM node into a XML string.
+     * 
+     * Parameters:
+     * node - {DOMElement} A DOM node.
+     *
+     * Returns:
+     * {String} The XML string representation of the input node.
+     */
+    write: function(node) {
+        var data;
+        if(this.xmldom) {
+            data = node.xml;
+        } else {
+            var serializer = new XMLSerializer();
+            if (node.nodeType == 1) {
+                // Add nodes to a document before serializing. Everything else
+                // is serialized as is. This may need more work. See #1218 .
+                var doc = document.implementation.createDocument("", "", null);
+                if (doc.importNode) {
+                    node = doc.importNode(node, true);
+                }
+                doc.appendChild(node);
+                data = serializer.serializeToString(doc);
+            } else {
+                data = serializer.serializeToString(node);
+            }
+        }
+        return data;
+    },
+
+    /**
+     * APIMethod: createElementNS
+     * Create a new element with namespace.  This node can be appended to
+     *     another node with the standard node.appendChild method.  For
+     *     cross-browser support, this method must be used instead of
+     *     document.createElementNS.
+     *
+     * Parameters:
+     * uri - {String} Namespace URI for the element.
+     * name - {String} The qualified name of the element (prefix:localname).
+     * 
+     * Returns:
+     * {Element} A DOM element with namespace.
+     */
+    createElementNS: function(uri, name) {
+        var element;
+        if(this.xmldom) {
+            if(typeof uri == "string") {
+                element = this.xmldom.createNode(1, name, uri);
+            } else {
+                element = this.xmldom.createNode(1, name, "");
+            }
+        } else {
+            element = document.createElementNS(uri, name);
+        }
+        return element;
+    },
+
+    /**
+     * APIMethod: createTextNode
+     * Create a text node.  This node can be appended to another node with
+     *     the standard node.appendChild method.  For cross-browser support,
+     *     this method must be used instead of document.createTextNode.
+     * 
+     * Parameters:
+     * text - {String} The text of the node.
+     * 
+     * Returns: 
+     * {DOMElement} A DOM text node.
+     */
+    createTextNode: function(text) {
+        var node;
+        if(this.xmldom) {
+            node = this.xmldom.createTextNode(text);
+        } else {
+            node = document.createTextNode(text);
+        }
+        return node;
+    },
+
+    /**
+     * APIMethod: getElementsByTagNameNS
+     * Get a list of elements on a node given the namespace URI and local name.
+     *     To return all nodes in a given namespace, use '*' for the name
+     *     argument.  To return all nodes of a given (local) name, regardless
+     *     of namespace, use '*' for the uri argument.
+     * 
+     * Parameters:
+     * node - {Element} Node on which to search for other nodes.
+     * uri - {String} Namespace URI.
+     * name - {String} Local name of the tag (without the prefix).
+     * 
+     * Returns:
+     * {NodeList} A node list or array of elements.
+     */
+    getElementsByTagNameNS: function(node, uri, name) {
+        var elements = [];
+        if(node.getElementsByTagNameNS) {
+            elements = node.getElementsByTagNameNS(uri, name);
+        } else {
+            // brute force method
+            var allNodes = node.getElementsByTagName("*");
+            var potentialNode, fullName;
+            for(var i=0, len=allNodes.length; i<len; ++i) {
+                potentialNode = allNodes[i];
+                fullName = (potentialNode.prefix) ?
+                           (potentialNode.prefix + ":" + name) : name;
+                if((name == "*") || (fullName == potentialNode.nodeName)) {
+                    if((uri == "*") || (uri == potentialNode.namespaceURI)) {
+                        elements.push(potentialNode);
+                    }
+                }
+            }
+        }
+        return elements;
+    },
+
+    /**
+     * APIMethod: getAttributeNodeNS
+     * Get an attribute node given the namespace URI and local name.
+     * 
+     * Parameters:
+     * node - {Element} Node on which to search for attribute nodes.
+     * uri - {String} Namespace URI.
+     * name - {String} Local name of the attribute (without the prefix).
+     * 
+     * Returns:
+     * {DOMElement} An attribute node or null if none found.
+     */
+    getAttributeNodeNS: function(node, uri, name) {
+        var attributeNode = null;
+        if(node.getAttributeNodeNS) {
+            attributeNode = node.getAttributeNodeNS(uri, name);
+        } else {
+            var attributes = node.attributes;
+            var potentialNode, fullName;
+            for(var i=0, len=attributes.length; i<len; ++i) {
+                potentialNode = attributes[i];
+                if(potentialNode.namespaceURI == uri) {
+                    fullName = (potentialNode.prefix) ?
+                               (potentialNode.prefix + ":" + name) : name;
+                    if(fullName == potentialNode.nodeName) {
+                        attributeNode = potentialNode;
+                        break;
+                    }
+                }
+            }
+        }
+        return attributeNode;
+    },
+
+    /**
+     * APIMethod: getAttributeNS
+     * Get an attribute value given the namespace URI and local name.
+     * 
+     * Parameters:
+     * node - {Element} Node on which to search for an attribute.
+     * uri - {String} Namespace URI.
+     * name - {String} Local name of the attribute (without the prefix).
+     * 
+     * Returns:
+     * {String} An attribute value or and empty string if none found.
+     */
+    getAttributeNS: function(node, uri, name) {
+        var attributeValue = "";
+        if(node.getAttributeNS) {
+            attributeValue = node.getAttributeNS(uri, name) || "";
+        } else {
+            var attributeNode = this.getAttributeNodeNS(node, uri, name);
+            if(attributeNode) {
+                attributeValue = attributeNode.nodeValue;
+            }
+        }
+        return attributeValue;
+    },
+    
+    /**
+     * APIMethod: getChildValue
+     * Get the value of the first child node if it exists, or return an
+     *     optional default string.  Returns an empty string if no first child
+     *     exists and no default value is supplied.
+     *
+     * Parameters:
+     * node - {DOMElement} The element used to look for a first child value.
+     * def - {String} Optional string to return in the event that no
+     *     first child value exists.
+     *
+     * Returns:
+     * {String} The value of the first child of the given node.
+     */
+    getChildValue: function(node, def) {
+        var value;
+        if (node && node.firstChild && node.firstChild.nodeValue) { 
+            value = node.firstChild.nodeValue;
+        } else {
+            value = (def != undefined) ? def : "";
+        }
+        return value;
+    },
+
+    /**
+     * APIMethod: concatChildValues
+     * Concatenate the value of all child nodes if any exist, or return an
+     *     optional default string.  Returns an empty string if no children
+     *     exist and no default value is supplied.  Not optimized for large
+     *     numbers of child nodes.
+     *
+     * Parameters:
+     * node - {DOMElement} The element used to look for child values.
+     * def - {String} Optional string to return in the event that no
+     *     child exist.
+     *
+     * Returns:
+     * {String} The concatenated value of all child nodes of the given node.
+     */
+    concatChildValues: function(node, def) {
+        var value = "";
+        var child = node.firstChild;
+        var childValue;
+        while(child) {
+            childValue = child.nodeValue;
+            if(childValue) {
+                value += childValue;
+            }
+            child = child.nextSibling;
+        }
+        if(value == "" && def != undefined) {
+            value = def;
+        }
+        return value;
+    },
+
+    /**
+     * APIMethod: hasAttributeNS
+     * Determine whether a node has a particular attribute matching the given
+     *     name and namespace.
+     * 
+     * Parameters:
+     * node - {Element} Node on which to search for an attribute.
+     * uri - {String} Namespace URI.
+     * name - {String} Local name of the attribute (without the prefix).
+     * 
+     * Returns:
+     * {Boolean} The node has an attribute matching the name and namespace.
+     */
+    hasAttributeNS: function(node, uri, name) {
+        var found = false;
+        if(node.hasAttributeNS) {
+            found = node.hasAttributeNS(uri, name);
+        } else {
+            found = !!this.getAttributeNodeNS(node, uri, name);
+        }
+        return found;
+    },
+    
+    /**
+     * APIMethod: setAttributeNS
+     * Adds a new attribute or changes the value of an attribute with the given
+     *     namespace and name.
+     *
+     * Parameters:
+     * node - {Element} Element node on which to set the attribute.
+     * uri - {String} Namespace URI for the attribute.
+     * name - {String} Qualified name (prefix:localname) for the attribute.
+     * value - {String} Attribute value.
+     */
+    setAttributeNS: function(node, uri, name, value) {
+        if(node.setAttributeNS) {
+            node.setAttributeNS(uri, name, value);
+        } else {
+            if(this.xmldom) {
+                if(uri) {
+                    var attribute = node.ownerDocument.createNode(
+                        2, name, uri
+                    );
+                    attribute.nodeValue = value;
+                    node.setAttributeNode(attribute);
+                } else {
+                    node.setAttribute(name, value);
+                }
+            } else {
+                throw "setAttributeNS not implemented";
+            }
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Format.XML" 
+
+});     
+/* ======================================================================
     OpenLayers/Handler.js
    ====================================================================== */
 
@@ -7768,7 +16070,7 @@
         }
         // register for event handlers defined on this class.
         var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
-        for (var i = 0; i < events.length; i++) {
+        for (var i=0, len=events.length; i<len; i++) {
             if (this[events[i]]) {
                 this.register(events[i], this[events[i]]); 
             }
@@ -7790,7 +16092,7 @@
         }
         // unregister event handlers defined on this class.
         var events = OpenLayers.Events.prototype.BROWSER_EVENTS;
-        for (var i = 0; i < events.length; i++) {
+        for (var i=0, len=events.length; i<len; i++) {
             if (this[events[i]]) {
                 this.unregister(events[i], this[events[i]]); 
             }
@@ -7924,7 +16226,13 @@
      * Constant: Z_INDEX_BASE
      * {Object} Base z-indexes for different classes of thing 
      */
-    Z_INDEX_BASE: { BaseLayer: 100, Overlay: 325, Popup: 750, Control: 1000 },
+    Z_INDEX_BASE: {
+        BaseLayer: 100,
+        Overlay: 325,
+        Feature: 725,
+        Popup: 750,
+        Control: 1000
+    },
 
     /**
      * Constant: EVENT_TYPES
@@ -7964,6 +16272,7 @@
      *  - *movestart* triggered after the start of a drag, pan, or zoom
      *  - *move* triggered after each drag, pan, or zoom
      *  - *moveend* triggered after a drag, pan, or zoom completes
+     *  - *zoomend* triggered after a zoom completes
      *  - *popupopen* triggered after a popup opens
      *  - *popupclose* triggered after a popup opens
      *  - *addmarker* triggered after a marker has been added
@@ -8097,6 +16406,13 @@
     zoom: 0,    
 
     /**
+     * Property: panRatio
+     * {Float} The ratio of the current extent within
+     *         which panning will tween.
+     */
+    panRatio: 1.5,    
+
+    /**
      * Property: viewRequestID
      * {String} Used to store a unique identifier that changes when the map 
      *          view changes. viewRequestID should be used when adding data 
@@ -8304,6 +16620,7 @@
         this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
 
         this.div = OpenLayers.Util.getElement(div);
+        OpenLayers.Element.addClass(this.div, 'olMap');
 
         // the viewPortDiv is the outermost div we modify
         var id = this.div.id + "_OpenLayers_ViewPort";
@@ -8325,7 +16642,8 @@
         this.events = new OpenLayers.Events(this, 
                                             this.div, 
                                             this.EVENT_TYPES, 
-                                            this.fallThrough);
+                                            this.fallThrough, 
+                                            {includeXY: true});
         this.updateSize();
         if(this.eventListeners instanceof Object) {
             this.events.on(this.eventListeners);
@@ -8354,7 +16672,7 @@
             // check existing links for equivalent url
             var addNode = true;
             var nodes = document.getElementsByTagName('link');
-            for(var i=0; i<nodes.length; ++i) {
+            for(var i=0, len=nodes.length; i<len; ++i) {
                 if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
                                                    this.theme)) {
                     addNode = false;
@@ -8386,7 +16704,7 @@
             }
         }
 
-        for(var i=0; i < this.controls.length; i++) {
+        for(var i=0, len=this.controls.length; i<len; i++) {
             this.addControlToMap(this.controls[i]);
         }
 
@@ -8639,7 +16957,7 @@
      */
     getLayer: function(id) {
         var foundLayer = null;
-        for (var i = 0; i < this.layers.length; i++) {
+        for (var i=0, len=this.layers.length; i<len; i++) {
             var layer = this.layers[i];
             if (layer.id == id) {
                 foundLayer = layer;
@@ -8667,7 +16985,7 @@
      * Reset each layer's z-index based on layer's array index
      */
     resetLayersZIndex: function() {
-        for (var i = 0; i < this.layers.length; i++) {
+        for (var i=0, len=this.layers.length; i<len; i++) {
             var layer = this.layers[i];
             this.setLayerZIndex(layer, i);
         }
@@ -8680,7 +16998,7 @@
     * layer - {<OpenLayers.Layer>} 
     */    
     addLayer: function (layer) {
-        for(var i=0; i < this.layers.length; i++) {
+        for(var i=0, len=this.layers.length; i <len; i++) {
             if (this.layers[i] == layer) {
                 var msg = OpenLayers.i18n('layerAlreadyAdded', 
                                                       {'layerName':layer.name});
@@ -8724,7 +17042,7 @@
     * layers - {Array(<OpenLayers.Layer>)} 
     */    
     addLayers: function (layers) {
-        for (var i = 0; i <  layers.length; i++) {
+        for (var i=0, len=layers.length; i<len; i++) {
             this.addLayer(layers[i]);
         }
     },
@@ -8775,7 +17093,7 @@
         if(this.baseLayer == layer) {
             this.baseLayer = null;
             if(setNewBaseLayer) {
-                for(var i=0; i < this.layers.length; i++) {
+                for(var i=0, len=this.layers.length; i<len; i++) {
                     var iLayer = this.layers[i];
                     if (iLayer.isBaseLayer) {
                         this.setBaseLayer(iLayer);
@@ -8836,7 +17154,7 @@
         if (base != idx) {
             this.layers.splice(base, 1);
             this.layers.splice(idx, 0, layer);
-            for (var i = 0; i < this.layers.length; i++) {
+            for (var i=0, len=this.layers.length; i<len; i++) {
                 this.setLayerZIndex(this.layers[i], i);
             }
             this.events.triggerEvent("changelayer", {
@@ -8854,7 +17172,7 @@
      *
      * Paremeters:
      * layer - {<OpenLayers.Layer>} 
-     * idx - {int} 
+     * delta - {int} 
      */
     raiseLayer: function (layer, delta) {
         var idx = this.getLayerIndex(layer) + delta;
@@ -8986,7 +17304,7 @@
      */    
     getControl: function (id) {
         var returnControl = null;
-        for(var i=0; i < this.controls.length; i++) {
+        for(var i=0, len=this.controls.length; i<len; i++) {
             var control = this.controls[i];
             if (control.id == id) {
                 returnControl = control;
@@ -9101,7 +17419,7 @@
      */
     updateSize: function() {
         // the div might have moved on the page, also
-        this.events.element.offsets = null;
+        this.events.clearMouseCache();
         var newSize = this.getCurrentSize();
         var oldSize = this.getSize();
         if (oldSize == null) {
@@ -9113,7 +17431,7 @@
             this.size = newSize;
 
             //notify layers of mapresize
-            for(var i=0; i < this.layers.length; i++) {
+            for(var i=0, len=this.layers.length; i<len; i++) {
                 this.layers[i].onMapResize();                
             }
 
@@ -9235,11 +17553,7 @@
      *    false.
      */
     pan: function(dx, dy, options) {
-        // this should be pushed to applyDefaults and extend
-        if (!options) {
-            options = {};
-        }
-        OpenLayers.Util.applyDefaults(options, {
+        options = OpenLayers.Util.applyDefaults(options, {
             animate: true,
             dragging: false
         });
@@ -9270,11 +17584,18 @@
      * lonlat - {<OpenLayers.Lonlat>}
      */
     panTo: function(lonlat) {
-        if (this.panMethod && this.getExtent().containsLonLat(lonlat)) {
+        if (this.panMethod && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
             if (!this.panTween) {
                 this.panTween = new OpenLayers.Tween(this.panMethod);
             }
             var center = this.getCenter();
+
+            // center will not change, don't do nothing
+            if (lonlat.lon == center.lon &&
+                lonlat.lat == center.lat) {
+                return;
+            }
+
             var from = {
                 lon: center.lon,
                 lat: center.lat
@@ -9436,7 +17757,7 @@
             
             bounds = this.baseLayer.getExtent();
             
-            for (var i = 0; i < this.layers.length; i++) {
+            for (var i=0, len=this.layers.length; i<len; i++) {
                 var layer = this.layers[i];
                 if (!layer.isBaseLayer) {
                     var inRange = layer.calculateInRange();
@@ -9455,13 +17776,16 @@
                     }
                     if (inRange && layer.visibility) {
                         layer.moveTo(bounds, zoomChanged, dragging);
+                        layer.events.triggerEvent("moveend",
+                            {"zoomChanged": zoomChanged}
+                        );
                     }
                 }                
             }
             
             if (zoomChanged) {
                 //redraw popups
-                for (var i = 0; i < this.popups.length; i++) {
+                for (var i=0, len=this.popups.length; i<len; i++) {
                     this.popups[i].updatePosition();
                 }
             }    
@@ -9591,13 +17915,25 @@
         
     /**
      * APIMethod: getMaxExtent
+     *
+     * Parameters:
+     * options - {Object} 
      * 
+     * Allowed Options:
+     * restricted - {Boolean} If true, returns restricted extent (if it is 
+     *     available.)
+     *
      * Returns:
-     * {<OpenLayers.Bounds>}
+     * {<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 () {
+    getMaxExtent: function (options) {
         var maxExtent = null;
-        if (this.baseLayer != null) {
+        if(options && options.restricted && this.restrictedExtent){
+            maxExtent = this.restrictedExtent;
+        } else if (this.baseLayer != null) {
             maxExtent = this.baseLayer.maxExtent;
         }        
         return maxExtent;
@@ -9660,6 +17996,21 @@
         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
       * 
@@ -9792,8 +18143,13 @@
      * 
      * 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.
+     * 
      */
-    zoomToExtent: function(bounds) {
+    zoomToExtent: function(bounds, closest) {
         var center = bounds.getCenterLonLat();
         if (this.baseLayer.wrapDateLine) {
             var maxExtent = this.getMaxExtent();
@@ -9817,15 +18173,28 @@
             //
             center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
         }
-        this.setCenter(center, this.getZoomForExtent(bounds));
+        this.setCenter(center, this.getZoomForExtent(bounds, closest));
     },
 
     /** 
      * APIMethod: zoomToMaxExtent
      * Zoom to the full extent and recenter.
+     *
+     * Parameters:
+     * options - 
+     * 
+     * Allowed Options:
+     * restricted - {Boolean} True to zoom to restricted extent if it is 
+     *     set. Defaults to true.
      */
-    zoomToMaxExtent: function() {
-        this.zoomToExtent(this.getMaxExtent());
+    zoomToMaxExtent: function(options) {
+        //restricted is true by default
+        var restricted = (options) ? options.restricted : true;
+
+        var maxExtent = this.getMaxExtent({
+            'restricted': restricted 
+        });
+        this.zoomToExtent(maxExtent);
     },
 
     /** 
@@ -9834,8 +18203,13 @@
      * 
      * 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) {
+    zoomToScale: function(scale, closest) {
         var res = OpenLayers.Util.getResolutionFromScale(scale, 
                                                          this.baseLayer.units);
         var size = this.getSize();
@@ -9847,7 +18221,7 @@
                                            center.lat - h_deg / 2,
                                            center.lon + w_deg / 2,
                                            center.lat + h_deg / 2);
-        this.zoomToExtent(extent);
+        this.zoomToExtent(extent, closest);
     },
     
   /********************************************************/
@@ -10108,9 +18482,9 @@
     
     /** 
      * Constructor: OpenLayers.Marker
-     * Paraemeters:
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>} the position of this marker
      * icon - {<OpenLayers.Icon>}  the icon for this marker
-     * lonlat - {<OpenLayers.LonLat>} the position of this marker
      */
     initialize: function(lonlat, icon) {
         this.lonlat = lonlat;
@@ -10262,6 +18636,2223 @@
     
 
 /* ======================================================================
+    OpenLayers/Popup/AnchoredBubble.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Popup/Anchored.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.AnchoredBubble
+ * 
+ * Inherits from: 
+ *  - <OpenLayers.Popup.Anchored>
+ */
+OpenLayers.Popup.AnchoredBubble = 
+  OpenLayers.Class(OpenLayers.Popup.Anchored, {
+
+    /**
+     * Property: rounded
+     * {Boolean} Has the popup been rounded yet?
+     */
+    rounded: false, 
+    
+    /** 
+     * Constructor: OpenLayers.Popup.AnchoredBubble
+     * 
+     * Parameters:
+     * id - {String}
+     * lonlat - {<OpenLayers.LonLat>}
+     * contentSize - {<OpenLayers.Size>}
+     * contentHTML - {String}
+     * anchor - {Object} Object to which we'll anchor the popup. Must expose 
+     *     a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>) 
+     *     (Note that this is generally an <OpenLayers.Icon>).
+     * closeBox - {Boolean}
+     * closeBoxCallback - {Function} Function to be called on closeBox click.
+     */
+    initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox,
+                        closeBoxCallback) {
+        
+        this.padding = new OpenLayers.Bounds(
+            0, OpenLayers.Popup.AnchoredBubble.CORNER_SIZE,
+            0, OpenLayers.Popup.AnchoredBubble.CORNER_SIZE
+        );
+        OpenLayers.Popup.Anchored.prototype.initialize.apply(this, arguments);
+    },
+
+    /** 
+     * Method: draw
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {DOMElement} Reference to a div that contains the drawn popup.
+     */
+    draw: function(px) {
+        
+        OpenLayers.Popup.Anchored.prototype.draw.apply(this, arguments);
+
+        this.setContentHTML();
+        
+        //set the popup color and opacity           
+        this.setBackgroundColor(); 
+        this.setOpacity();
+
+        return this.div;
+    },
+
+    /**
+     * Method: updateRelativePosition
+     * The popup has been moved to a new relative location, in which case
+     *     we will want to re-do the rico corners.
+     */
+    updateRelativePosition: function() {
+        this.setRicoCorners();
+    },
+
+    /**
+     * APIMethod: setSize
+     * 
+     * Parameters:
+     * contentSize - {<OpenLayers.Size>} the new size for the popup's 
+     *     contents div (in pixels).
+     */
+    setSize:function(contentSize) { 
+        OpenLayers.Popup.Anchored.prototype.setSize.apply(this, arguments);
+
+        this.setRicoCorners();
+    },  
+
+    /**
+     * APIMethod: setBackgroundColor
+     * 
+     * Parameters:
+     * color - {String}
+     */
+    setBackgroundColor:function(color) { 
+        if (color != undefined) {
+            this.backgroundColor = color; 
+        }
+        
+        if (this.div != null) {
+            if (this.contentDiv != null) {
+                this.div.style.background = "transparent";
+                OpenLayers.Rico.Corner.changeColor(this.groupDiv, 
+                                                   this.backgroundColor);
+            }
+        }
+    },  
+    
+    /**
+     * APIMethod: setOpacity
+     * 
+     * Parameters: 
+     * opacity - {float}
+     */
+    setOpacity:function(opacity) { 
+        OpenLayers.Popup.Anchored.prototype.setOpacity.call(this, opacity);
+        
+        if (this.div != null) {
+            if (this.groupDiv != null) {
+                OpenLayers.Rico.Corner.changeOpacity(this.groupDiv, 
+                                                     this.opacity);
+            }
+        }
+    },  
+ 
+    /** 
+     * Method: setBorder
+     * Always sets border to 0. Bubble Popups can not have a border.
+     * 
+     * Parameters:
+     * border - {Integer}
+     */
+    setBorder:function(border) { 
+        this.border = 0;
+    },      
+ 
+    /** 
+     * Method: setRicoCorners
+     * Update RICO corners according to the popup's current relative postion.
+     */
+    setRicoCorners:function() {
+    
+        var corners = this.getCornersToRound(this.relativePosition);
+        var options = {corners: corners,
+                         color: this.backgroundColor,
+                       bgColor: "transparent",
+                         blend: false};
+
+        if (!this.rounded) {
+            OpenLayers.Rico.Corner.round(this.div, options);
+            this.rounded = true;
+        } else {
+            OpenLayers.Rico.Corner.reRound(this.groupDiv, options);
+            //set the popup color and opacity
+            this.setBackgroundColor(); 
+            this.setOpacity();
+        }
+    },
+
+    /** 
+     * Method: getCornersToRound
+     *  
+     * Returns:
+     * {String} The proper corners string ("tr tl bl br") for rico to round.
+     */
+    getCornersToRound:function() {
+
+        var corners = ['tl', 'tr', 'bl', 'br'];
+
+        //we want to round all the corners _except_ the opposite one. 
+        var corner = OpenLayers.Bounds.oppositeQuadrant(this.relativePosition);
+        OpenLayers.Util.removeItem(corners, corner);
+
+        return corners.join(" ");
+    },
+
+    CLASS_NAME: "OpenLayers.Popup.AnchoredBubble"
+});
+
+/**
+ * Constant: CORNER_SIZE
+ * {Integer} 5. Border space for the RICO corners.
+ */
+OpenLayers.Popup.AnchoredBubble.CORNER_SIZE = 5;
+
+/* ======================================================================
+    OpenLayers/Popup/Framed.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Popup/Anchored.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.Framed
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Popup.Anchored>
+ */
+OpenLayers.Popup.Framed =
+  OpenLayers.Class(OpenLayers.Popup.Anchored, {
+
+    /**
+     * Property: imageSrc
+     * {String} location of the image to be used as the popup frame
+     */
+    imageSrc: null,
+
+    /**
+     * Property: imageSize
+     * {<OpenLayers.Size>} Size (measured in pixels) of the image located
+     *     by the 'imageSrc' property.
+     */
+    imageSize: null,
+
+    /**
+     * APIProperty: isAlphaImage
+     * {Boolean} The image has some alpha and thus needs to use the alpha 
+     *     image hack. Note that setting this to true will have no noticeable
+     *     effect in FF or IE7 browsers, but will all but crush the ie6 
+     *     browser. 
+     *     Default is false.
+     */
+    isAlphaImage: false,
+
+    /**
+     * Property: positionBlocks
+     * {Object} Hash of different position blocks (Object/Hashs). Each block 
+     *     will be keyed by a two-character 'relativePosition' 
+     *     code string (ie "tl", "tr", "bl", "br"). Block properties are 
+     *     'offset', 'padding' (self-explanatory), and finally the 'blocks'
+     *     parameter, which is an array of the block objects. 
+     * 
+     *     Each block object must have 'size', 'anchor', and 'position' 
+     *     properties.
+     * 
+     *     Note that positionBlocks should never be modified at runtime.
+     */
+    positionBlocks: null,
+
+    /**
+     * Property: blocks
+     * {Array[Object]} Array of objects, each of which is one "block" of the 
+     *     popup. Each block has a 'div' and an 'image' property, both of 
+     *     which are DOMElements, and the latter of which is appended to the 
+     *     former. These are reused as the popup goes changing positions for
+     *     great economy and elegance.
+     */
+    blocks: null,
+
+    /** 
+     * APIProperty: fixedRelativePosition
+     * {Boolean} We want the framed popup to work dynamically placed relative
+     *     to its anchor but also in just one fixed position. A well designed
+     *     framed popup will have the pixels and logic to display itself in 
+     *     any of the four relative positions, but (understandably), this will
+     *     not be the case for all of them. By setting this property to 'true', 
+     *     framed popup will not recalculate for the best placement each time
+     *     it's open, but will always open the same way. 
+     *     Note that if this is set to true, it is generally advisable to also
+     *     set the 'panIntoView' property to true so that the popup can be 
+     *     scrolled into view (since it will often be offscreen on open)
+     *     Default is false.
+     */
+    fixedRelativePosition: false,
+
+    /** 
+     * Constructor: OpenLayers.Popup.Framed
+     * 
+     * Parameters:
+     * id - {String}
+     * lonlat - {<OpenLayers.LonLat>}
+     * contentSize - {<OpenLayers.Size>}
+     * contentHTML - {String}
+     * anchor - {Object} Object to which we'll anchor the popup. Must expose 
+     *     a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>) 
+     *     (Note that this is generally an <OpenLayers.Icon>).
+     * closeBox - {Boolean}
+     * closeBoxCallback - {Function} Function to be called on closeBox click.
+     */
+    initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox, 
+                        closeBoxCallback) {
+
+        OpenLayers.Popup.Anchored.prototype.initialize.apply(this, arguments);
+
+        if (this.fixedRelativePosition) {
+            //based on our decided relativePostion, set the current padding
+            // this keeps us from getting into trouble 
+            this.updateRelativePosition();
+            
+            //make calculateRelativePosition always returnt the specified
+            // fiexed position.
+            this.calculateRelativePosition = function(px) {
+                return this.relativePosition;
+            };
+        }
+
+        this.contentDiv.style.position = "absolute";
+        this.contentDiv.style.zIndex = 1;
+
+        if (closeBox) {
+            this.closeDiv.style.zIndex = 1;
+        }
+
+        this.groupDiv.style.position = "absolute";
+        this.groupDiv.style.top = "0px";
+        this.groupDiv.style.left = "0px";
+        this.groupDiv.style.height = "100%";
+        this.groupDiv.style.width = "100%";
+    },
+
+    /** 
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        this.imageSrc = null;
+        this.imageSize = null;
+        this.isAlphaImage = null;
+
+        this.fixedRelativePosition = false;
+        this.positionBlocks = null;
+
+        //remove our blocks
+        for(var i = 0; i < this.blocks.length; i++) {
+            var block = this.blocks[i];
+
+            if (block.image) {
+                block.div.removeChild(block.image);
+            }
+            block.image = null;
+
+            if (block.div) {
+                this.groupDiv.removeChild(block.div);
+            }
+            block.div = null;
+        }
+        this.blocks = null;
+
+        OpenLayers.Popup.Anchored.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * APIMethod: setBackgroundColor
+     */
+    setBackgroundColor:function(color) {
+        //does nothing since the framed popup's entire scheme is based on a 
+        // an image -- changing the background color makes no sense. 
+    },
+
+    /**
+     * APIMethod: setBorder
+     */
+    setBorder:function() {
+        //does nothing since the framed popup's entire scheme is based on a 
+        // an image -- changing the popup's border makes no sense. 
+    },
+
+    /**
+     * Method: setOpacity
+     * Sets the opacity of the popup.
+     * 
+     * Parameters:
+     * opacity - {float} A value between 0.0 (transparent) and 1.0 (solid).   
+     */
+    setOpacity:function(opacity) {
+        //does nothing since we suppose that we'll never apply an opacity
+        // to a framed popup
+    },
+
+    /**
+     * APIMethod: setSize
+     * Overridden here, because we need to update the blocks whenever the size
+     *     of the popup has changed.
+     * 
+     * Parameters:
+     * contentSize - {<OpenLayers.Size>} the new size for the popup's 
+     *     contents div (in pixels).
+     */
+    setSize:function(contentSize) { 
+        OpenLayers.Popup.Anchored.prototype.setSize.apply(this, arguments);
+
+        this.updateBlocks();
+    },
+
+    /**
+     * Method: updateRelativePosition
+     * When the relative position changes, we need to set the new padding 
+     *     BBOX on the popup, reposition the close div, and update the blocks.
+     */
+    updateRelativePosition: function() {
+
+        //update the padding
+        this.padding = this.positionBlocks[this.relativePosition].padding;
+
+        //update the position of our close box to new padding
+        if (this.closeDiv) {
+            // use the content div's css padding to determine if we should
+            //  padd the close div
+            var contentDivPadding = this.getContentDivPadding();
+
+            this.closeDiv.style.right = contentDivPadding.right + 
+                                        this.padding.right + "px";
+            this.closeDiv.style.top = contentDivPadding.top + 
+                                      this.padding.top + "px";
+        }
+
+        this.updateBlocks();
+    },
+
+    /** 
+     * Method: calculateNewPx
+     * Besides the standard offset as determined by the Anchored class, our 
+     *     Framed popups have a special 'offset' property for each of their 
+     *     positions, which is used to offset the popup relative to its anchor.
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {<OpenLayers.Pixel>} The the new px position of the popup on the screen
+     *     relative to the passed-in px.
+     */
+    calculateNewPx:function(px) {
+        var newPx = OpenLayers.Popup.Anchored.prototype.calculateNewPx.apply(
+            this, arguments
+        );
+
+        newPx = newPx.offset(this.positionBlocks[this.relativePosition].offset);
+
+        return newPx;
+    },
+
+    /**
+     * Method: createBlocks
+     */
+    createBlocks: function() {
+        this.blocks = [];
+
+        //since all positions contain the same number of blocks, we can 
+        // just pick the first position and use its blocks array to create
+        // our blocks array
+        var firstPosition = null;
+        for(var key in this.positionBlocks) {
+            firstPosition = key;
+            break;
+        }
+        
+        var position = this.positionBlocks[firstPosition];
+        for (var i = 0; i < position.blocks.length; i++) {
+
+            var block = {};
+            this.blocks.push(block);
+
+            var divId = this.id + '_FrameDecorationDiv_' + i;
+            block.div = OpenLayers.Util.createDiv(divId, 
+                null, null, null, "absolute", null, "hidden", null
+            );
+
+            var imgId = this.id + '_FrameDecorationImg_' + i;
+            var imageCreator = 
+                (this.isAlphaImage) ? OpenLayers.Util.createAlphaImageDiv
+                                    : OpenLayers.Util.createImage;
+
+            block.image = imageCreator(imgId, 
+                null, this.imageSize, this.imageSrc, 
+                "absolute", null, null, null
+            );
+
+            block.div.appendChild(block.image);
+            this.groupDiv.appendChild(block.div);
+        }
+    },
+
+    /**
+     * Method: updateBlocks
+     * Internal method, called on initialize and when the popup's relative
+     *     position has changed. This function takes care of re-positioning
+     *     the popup's blocks in their appropropriate places.
+     */
+    updateBlocks: function() {
+        if (!this.blocks) {
+            this.createBlocks();
+        }
+        
+        if (this.size && this.relativePosition) {
+            var position = this.positionBlocks[this.relativePosition];
+            for (var i = 0; i < position.blocks.length; i++) {
+    
+                var positionBlock = position.blocks[i];
+                var block = this.blocks[i];
+    
+                // adjust sizes
+                var l = positionBlock.anchor.left;
+                var b = positionBlock.anchor.bottom;
+                var r = positionBlock.anchor.right;
+                var t = positionBlock.anchor.top;
+    
+                //note that we use the isNaN() test here because if the 
+                // size object is initialized with a "auto" parameter, the 
+                // size constructor calls parseFloat() on the string, 
+                // which will turn it into NaN
+                //
+                var w = (isNaN(positionBlock.size.w)) ? this.size.w - (r + l) 
+                                                      : positionBlock.size.w;
+    
+                var h = (isNaN(positionBlock.size.h)) ? this.size.h - (b + t) 
+                                                      : positionBlock.size.h;
+    
+                block.div.style.width = w + 'px';
+                block.div.style.height = h + 'px';
+    
+                block.div.style.left = (l != null) ? l + 'px' : '';
+                block.div.style.bottom = (b != null) ? b + 'px' : '';
+                block.div.style.right = (r != null) ? r + 'px' : '';            
+                block.div.style.top = (t != null) ? t + 'px' : '';
+    
+                block.image.style.left = positionBlock.position.x + 'px';
+                block.image.style.top = positionBlock.position.y + 'px';
+            }
+    
+            this.contentDiv.style.left = this.padding.left + "px";
+            this.contentDiv.style.top = this.padding.top + "px";
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Popup.Framed"
+});
+/* ======================================================================
+    OpenLayers/Renderer/SVG.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.SVG
+ * 
+ * Inherits:
+ *  - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.SVG = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+    /** 
+     * Property: xmlns
+     * {String}
+     */
+    xmlns: "http://www.w3.org/2000/svg",
+    
+    /**
+     * Property: xlinkns
+     * {String}
+     */
+    xlinkns: "http://www.w3.org/1999/xlink",
+
+    /**
+     * Constant: MAX_PIXEL
+     * {Integer} Firefox has a limitation where values larger or smaller than  
+     *           about 15000 in an SVG document lock the browser up. This 
+     *           works around it.
+     */
+    MAX_PIXEL: 15000,
+
+    /**
+     * Property: translationParameters
+     * {Object} Hash with "x" and "y" properties
+     */
+    translationParameters: null,
+    
+    /**
+     * Property: symbolSize
+     * {Object} Cache for symbol sizes according to their svg coordinate space
+     */
+    symbolSize: {},
+
+    /**
+     * Constructor: OpenLayers.Renderer.SVG
+     * 
+     * Parameters:
+     * containerID - {String}
+     */
+    initialize: function(containerID) {
+        if (!this.supported()) { 
+            return; 
+        }
+        OpenLayers.Renderer.Elements.prototype.initialize.apply(this, 
+                                                                arguments);
+        this.translationParameters = {x: 0, y: 0};
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * APIMethod: supported
+     * 
+     * Returns:
+     * {Boolean} Whether or not the browser supports the SVG renderer
+     */
+    supported: function() {
+        var svgFeature = "http://www.w3.org/TR/SVG11/feature#";
+        return (document.implementation && 
+           (document.implementation.hasFeature("org.w3c.svg", "1.0") || 
+            document.implementation.hasFeature(svgFeature + "SVG", "1.1") || 
+            document.implementation.hasFeature(svgFeature + "BasicStructure", "1.1") ));
+    },    
+
+    /**
+     * Method: inValidRange
+     * See #669 for more information
+     *
+     * Parameters:
+     * x      - {Integer}
+     * y      - {Integer}
+     * xyOnly - {Boolean} whether or not to just check for x and y, which means
+     *     to not take the current translation parameters into account if true.
+     * 
+     * Returns:
+     * {Boolean} Whether or not the 'x' and 'y' coordinates are in the  
+     *           valid range.
+     */ 
+    inValidRange: function(x, y, xyOnly) {
+        var left = x + (xyOnly ? 0 : this.translationParameters.x);
+        var top = y + (xyOnly ? 0 : this.translationParameters.y);
+        return (left >= -this.MAX_PIXEL && left <= this.MAX_PIXEL &&
+                top >= -this.MAX_PIXEL && top <= this.MAX_PIXEL);
+    },
+
+    /**
+     * Method: setExtent
+     * 
+     * Parameters:
+     * extent - {<OpenLayers.Bounds>}
+     * resolutionChanged - {Boolean}
+     * 
+     * Returns:
+     * {Boolean} true to notify the layer that the new extent does not exceed
+     *     the coordinate range, and the features will not need to be redrawn.
+     *     False otherwise.
+     */
+    setExtent: function(extent, resolutionChanged) {
+        OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, 
+                                                               arguments);
+        
+        var resolution = this.getResolution();
+        var left = -extent.left / resolution;
+        var top = extent.top / resolution;
+
+        // If the resolution has changed, start over changing the corner, because
+        // the features will redraw.
+        if (resolutionChanged) {
+            this.left = left;
+            this.top = top;
+            // Set the viewbox
+            var extentString = "0 0 " + this.size.w + " " + this.size.h;
+
+            this.rendererRoot.setAttributeNS(null, "viewBox", extentString);
+            this.translate(0, 0);
+            return true;
+        } else {
+            var inRange = this.translate(left - this.left, top - this.top);
+            if (!inRange) {
+                // recenter the coordinate system
+                this.setExtent(extent, true);
+            }
+            return inRange;
+        }
+    },
+    
+    /**
+     * Method: translate
+     * Transforms the SVG coordinate system
+     * 
+     * Parameters:
+     * x - {Float}
+     * y - {Float}
+     * 
+     * Returns:
+     * {Boolean} true if the translation parameters ar in the valid coordinates
+     *     range, false otherwise.
+     */
+    translate: function(x, y) {
+        if (!this.inValidRange(x, y, true)) {
+            return false;
+        } else {
+            var transformString = "";
+            if (x || y) {
+                transformString = "translate(" + x + "," + y + ")";
+            }
+            this.root.setAttributeNS(null, "transform", transformString);
+            this.translationParameters = {x: x, y: y};
+            return true;
+        }
+    },
+
+    /**
+     * Method: setSize
+     * Sets the size of the drawing surface.
+     * 
+     * Parameters:
+     * size - {<OpenLayers.Size>} The size of the drawing surface
+     */
+    setSize: function(size) {
+        OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+        
+        this.rendererRoot.setAttributeNS(null, "width", this.size.w);
+        this.rendererRoot.setAttributeNS(null, "height", this.size.h);
+    },
+
+    /** 
+     * Method: getNodeType 
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     * 
+     * Returns:
+     * {String} The corresponding node type for the specified geometry
+     */
+    getNodeType: function(geometry, style) {
+        var nodeType = null;
+        switch (geometry.CLASS_NAME) {
+            case "OpenLayers.Geometry.Point":
+                if (style.externalGraphic) {
+                    nodeType = "image";
+                } else if (this.isComplexSymbol(style.graphicName)) {
+                    nodeType = "use";
+                } else {
+                    nodeType = "circle";
+                }
+                break;
+            case "OpenLayers.Geometry.Rectangle":
+                nodeType = "rect";
+                break;
+            case "OpenLayers.Geometry.LineString":
+                nodeType = "polyline";
+                break;
+            case "OpenLayers.Geometry.LinearRing":
+                nodeType = "polygon";
+                break;
+            case "OpenLayers.Geometry.Polygon":
+            case "OpenLayers.Geometry.Curve":
+            case "OpenLayers.Geometry.Surface":
+                nodeType = "path";
+                break;
+            default:
+                break;
+        }
+        return nodeType;
+    },
+
+    /** 
+     * Method: setStyle
+     * Use to set all the style attributes to a SVG node.
+     * 
+     * Takes care to adjust stroke width and point radius to be
+     * resolution-relative
+     *
+     * Parameters:
+     * node - {SVGDomElement} An SVG element to decorate
+     * style - {Object}
+     * options - {Object} Currently supported options include 
+     *                              'isFilled' {Boolean} and
+     *                              'isStroked' {Boolean}
+     */
+    setStyle: function(node, style, options) {
+        style = style  || node._style;
+        options = options || node._options;
+        var r = parseFloat(node.getAttributeNS(null, "r"));
+        var widthFactor = 1;
+        var pos;
+        if (node._geometryClass == "OpenLayers.Geometry.Point" && r) {
+            if (style.externalGraphic) {
+                pos = this.getPosition(node);
+                
+                if (style.graphicWidth && style.graphicHeight) {
+                  node.setAttributeNS(null, "preserveAspectRatio", "none");
+                }
+                var width = style.graphicWidth || style.graphicHeight;
+                var height = style.graphicHeight || style.graphicWidth;
+                width = width ? width : style.pointRadius*2;
+                height = height ? height : style.pointRadius*2;
+                var xOffset = (style.graphicXOffset != undefined) ?
+                    style.graphicXOffset : -(0.5 * width);
+                var yOffset = (style.graphicYOffset != undefined) ?
+                    style.graphicYOffset : -(0.5 * height);
+
+                var opacity = style.graphicOpacity || style.fillOpacity;
+                
+                node.setAttributeNS(null, "x", (pos.x + xOffset).toFixed());
+                node.setAttributeNS(null, "y", (pos.y + yOffset).toFixed());
+                node.setAttributeNS(null, "width", width);
+                node.setAttributeNS(null, "height", height);
+                node.setAttributeNS(this.xlinkns, "href", style.externalGraphic);
+                node.setAttributeNS(null, "style", "opacity: "+opacity);
+            } else if (this.isComplexSymbol(style.graphicName)) {
+                // the symbol viewBox is three times as large as the symbol
+                var offset = style.pointRadius * 3;
+                var size = offset * 2;
+                var id = this.importSymbol(style.graphicName);
+                var href = "#" + id;
+                pos = this.getPosition(node);
+                widthFactor = this.symbolSize[id] / size;
+                // Only set the href if it is different from the current one.
+                // This is a workaround for strange rendering behavior in FF3.
+                if (node.getAttributeNS(this.xlinkns, "href") != href) {
+                    node.setAttributeNS(this.xlinkns, "href", href);
+                } else if (size != parseFloat(node.getAttributeNS(null, "width"))) {
+                    // hide the element (and force a reflow so it really gets
+                    // hidden. This workaround is needed for Safari.
+                    node.style.visibility = "hidden";
+                    this.container.scrollLeft = this.container.scrollLeft;
+                }
+                node.setAttributeNS(null, "width", size);
+                node.setAttributeNS(null, "height", size);
+                node.setAttributeNS(null, "x", pos.x - offset);
+                node.setAttributeNS(null, "y", pos.y - offset);
+                // set the visibility back to normal (after the Safari
+                // workaround above)
+                node.style.visibility = "";
+            } else {
+                node.setAttributeNS(null, "r", style.pointRadius);
+            }
+
+            if (typeof style.rotation != "undefined" && pos) {
+                var rotation = OpenLayers.String.format(
+                    "rotate(${0} ${1} ${2})", [style.rotation, pos.x, pos.y]);
+                node.setAttributeNS(null, "transform", rotation);
+            }
+        }
+        
+        if (options.isFilled) {
+            node.setAttributeNS(null, "fill", style.fillColor);
+            node.setAttributeNS(null, "fill-opacity", style.fillOpacity);
+        } else {
+            node.setAttributeNS(null, "fill", "none");
+        }
+
+        if (options.isStroked) {
+            node.setAttributeNS(null, "stroke", style.strokeColor);
+            node.setAttributeNS(null, "stroke-opacity", style.strokeOpacity);
+            node.setAttributeNS(null, "stroke-width", style.strokeWidth * widthFactor);
+            node.setAttributeNS(null, "stroke-linecap", style.strokeLinecap);
+            // Hard-coded linejoin for now, to make it look the same as in VML.
+            // There is no strokeLinejoin property yet for symbolizers.
+            node.setAttributeNS(null, "stroke-linejoin", "round");
+            node.setAttributeNS(null, "stroke-dasharray", this.dashStyle(style,
+                widthFactor));
+        } else {
+            node.setAttributeNS(null, "stroke", "none");
+        }
+        
+        if (style.pointerEvents) {
+            node.setAttributeNS(null, "pointer-events", style.pointerEvents);
+        }
+        
+        if (style.cursor != null) {
+            node.setAttributeNS(null, "cursor", style.cursor);
+        }
+        return node;
+    },
+
+    /** 
+     * Method: dashStyle
+     * 
+     * Parameters:
+     * style - {Object}
+     * widthFactor - {Number}
+     * 
+     * Returns:
+     * {String} A SVG compliant 'stroke-dasharray' value
+     */
+    dashStyle: function(style, widthFactor) {
+        var w = style.strokeWidth * widthFactor;
+
+        switch (style.strokeDashstyle) {
+            case 'solid':
+                return 'none';
+            case 'dot':
+                return [1, 4 * w].join();
+            case 'dash':
+                return [4 * w, 4 * w].join();
+            case 'dashdot':
+                return [4 * w, 4 * w, 1, 4 * w].join();
+            case 'longdash':
+                return [8 * w, 4 * w].join();
+            case 'longdashdot':
+                return [8 * w, 4 * w, 1, 4 * w].join();
+            default:
+                return style.strokeDashstyle.replace(/ /g, ",");
+        }
+    },
+    
+    /** 
+     * Method: createNode
+     * 
+     * Parameters:
+     * type - {String} Kind of node to draw
+     * id - {String} Id for node
+     * 
+     * Returns:
+     * {DOMElement} A new node of the given type and id
+     */
+    createNode: function(type, id) {
+        var node = document.createElementNS(this.xmlns, type);
+        if (id) {
+            node.setAttributeNS(null, "id", id);
+        }
+        return node;    
+    },
+    
+    /** 
+     * Method: nodeTypeCompare
+     * 
+     * Parameters:
+     * node - {SVGDomElement} An SVG element
+     * type - {String} Kind of node
+     * 
+     * Returns:
+     * {Boolean} Whether or not the specified node is of the specified type
+     */
+    nodeTypeCompare: function(node, type) {
+        return (type == node.nodeName);
+    },
+   
+    /**
+     * Method: createRenderRoot
+     * 
+     * Returns:
+     * {DOMElement} The specific render engine's root element
+     */
+    createRenderRoot: function() {
+        return this.nodeFactory(this.container.id + "_svgRoot", "svg");
+    },
+
+    /**
+     * Method: createRoot
+     * 
+     * Returns:
+     * {DOMElement} The main root element to which we'll add vectors
+     */
+    createRoot: function() {
+        return this.nodeFactory(this.container.id + "_root", "g");
+    },
+
+    /**
+     * Method: createDefs
+     *
+     * Returns:
+     * {DOMElement} The element to which we'll add the symbol definitions
+     */
+    createDefs: function() {
+        var defs = this.nodeFactory("ol-renderer-defs", "defs");
+        this.rendererRoot.appendChild(defs);
+        return defs;
+    },
+
+    /**************************************
+     *                                    *
+     *     GEOMETRY DRAWING FUNCTIONS     *
+     *                                    *
+     **************************************/
+
+    /**
+     * Method: drawPoint
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the point
+     */ 
+    drawPoint: function(node, geometry) {
+        return this.drawCircle(node, geometry, 1);
+    },
+
+    /**
+     * Method: drawCircle
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * radius - {Float}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the circle
+     */
+    drawCircle: function(node, geometry, radius) {
+        var resolution = this.getResolution();
+        var x = (geometry.x / resolution + this.left);
+        var y = (this.top - geometry.y / resolution);
+
+        if (this.inValidRange(x, y)) { 
+            node.setAttributeNS(null, "cx", x);
+            node.setAttributeNS(null, "cy", y);
+            node.setAttributeNS(null, "r", radius);
+            return node;
+        } else {
+            return false;
+        }    
+            
+    },
+    
+    /**
+     * Method: drawLineString
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components of
+     *     the linestring, or false if nothing could be drawn
+     */ 
+    drawLineString: function(node, geometry) {
+        var componentsResult = this.getComponentsString(geometry.components);
+        if (componentsResult.path) {
+            node.setAttributeNS(null, "points", componentsResult.path);
+            return (componentsResult.complete ? node : null);  
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: drawLinearRing
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components
+     *     of the linear ring, or false if nothing could be drawn
+     */ 
+    drawLinearRing: function(node, geometry) {
+        var componentsResult = this.getComponentsString(geometry.components);
+        if (componentsResult.path) {
+            node.setAttributeNS(null, "points", componentsResult.path);
+            return (componentsResult.complete ? node : null);  
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: drawPolygon
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or null if the renderer could not draw all components
+     *     of the polygon, or false if nothing could be drawn
+     */ 
+    drawPolygon: function(node, geometry) {
+        var d = "";
+        var draw = true;
+        var complete = true;
+        var linearRingResult, path;
+        for (var j=0, len=geometry.components.length; j<len; j++) {
+            d += " M";
+            linearRingResult = this.getComponentsString(
+                geometry.components[j].components, " ");
+            path = linearRingResult.path;
+            if (path) {
+                d += " " + path;
+                complete = linearRingResult.complete && complete;
+            } else {
+                draw = false;
+            }
+        }
+        d += " z";
+        if (draw) {
+            node.setAttributeNS(null, "d", d);
+            node.setAttributeNS(null, "fill-rule", "evenodd");
+            return complete ? node : null;
+        } else {
+            return false;
+        }    
+    },
+    
+    /**
+     * Method: drawRectangle
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the rectangle
+     */ 
+    drawRectangle: function(node, geometry) {
+        var resolution = this.getResolution();
+        var x = (geometry.x / resolution + this.left);
+        var y = (this.top - geometry.y / resolution);
+
+        if (this.inValidRange(x, y)) { 
+            node.setAttributeNS(null, "x", x);
+            node.setAttributeNS(null, "y", y);
+            node.setAttributeNS(null, "width", geometry.width / resolution);
+            node.setAttributeNS(null, "height", geometry.height / resolution);
+            return node;
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: drawSurface
+     * This method is only called by the renderer itself.
+     * 
+     * Parameters: 
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the renderer could not draw the surface
+     */ 
+    drawSurface: function(node, geometry) {
+
+        // create the svg path string representation
+        var d = null;
+        var draw = true;
+        for (var i=0, len=geometry.components.length; i<len; i++) {
+            if ((i%3) == 0 && (i/3) == 0) {
+                var component = this.getShortString(geometry.components[i]);
+                if (!component) { draw = false; }
+                d = "M " + component;
+            } else if ((i%3) == 1) {
+                var component = this.getShortString(geometry.components[i]);
+                if (!component) { draw = false; }
+                d += " C " + component;
+            } else {
+                var component = this.getShortString(geometry.components[i]);
+                if (!component) { draw = false; }
+                d += " " + component;
+            }
+        }
+        d += " Z";
+        if (draw) {
+            node.setAttributeNS(null, "d", d);
+            return node;
+        } else {
+            return false;
+        }    
+    },
+
+    /** 
+     * Method: getComponentString
+     * 
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry.Point>)} Array of points
+     * separator - {String} character between coordinate pairs. Defaults to ","
+     * 
+     * Returns:
+     * {Object} hash with properties "path" (the string created from the
+     *     components and "complete" (false if the renderer was unable to
+     *     draw all components)
+     */
+    getComponentsString: function(components, separator) {
+        var renderCmp = [];
+        var complete = true;
+        var len = components.length;
+        var strings = [];
+        var str, component, j;
+        for(var i=0; i<len; i++) {
+            component = components[i];
+            renderCmp.push(component);
+            str = this.getShortString(component);
+            if (str) {
+                strings.push(str);
+            } else {
+                // The current component is outside the valid range. Let's
+                // see if the previous or next component is inside the range.
+                // If so, add the coordinate of the intersection with the
+                // valid range bounds.
+                if (i > 0) {
+                    if (this.getShortString(components[i + 1])) {
+                        strings.push(this.clipLine(components[i],
+                            components[i-1]));
+                    }
+                }
+                if (i < len - 1) {
+                    if (this.getShortString(components[i + 1])) {
+                        strings.push(this.clipLine(components[i],
+                            components[i+1]));
+                    }
+                }
+                complete = false;
+            }
+        }
+
+        return {
+            path: strings.join(separator || ","),
+            complete: complete
+        };
+    },
+    
+    /**
+     * Method: clipLine
+     * Given two points (one inside the valid range, and one outside),
+     * clips the line betweeen the two points so that the new points are both
+     * inside the valid range.
+     * 
+     * Parameters:
+     * badComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
+     *     invalid point
+     * goodComponent - {<OpenLayers.Geometry.Point>)} original geometry of the
+     *     valid point
+     * Returns
+     * {String} the SVG coordinate pair of the clipped point (like
+     *     getShortString), or an empty string if both passed componets are at
+     *     the same point.
+     */
+    clipLine: function(badComponent, goodComponent) {
+        if (goodComponent.equals(badComponent)) {
+            return "";
+        }
+        var resolution = this.getResolution();
+        var maxX = this.MAX_PIXEL - this.translationParameters.x;
+        var maxY = this.MAX_PIXEL - this.translationParameters.y;
+        var x1 = goodComponent.x / resolution + this.left;
+        var y1 = this.top - goodComponent.y / resolution;
+        var x2 = badComponent.x / resolution + this.left;
+        var y2 = this.top - badComponent.y / resolution;
+        var k;
+        if (x2 < -maxX || x2 > maxX) {
+            k = (y2 - y1) / (x2 - x1);
+            x2 = x2 < 0 ? -maxX : maxX;
+            y2 = y1 + (x2 - x1) * k;
+        }
+        if (y2 < -maxY || y2 > maxY) {
+            k = (x2 - x1) / (y2 - y1);
+            y2 = y2 < 0 ? -maxY : maxY;
+            x2 = x1 + (y2 - y1) * k;
+        }
+        return x2 + "," + y2;
+    },
+
+    /** 
+     * Method: getShortString
+     * 
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>}
+     * 
+     * Returns:
+     * {String} or false if point is outside the valid range
+     */
+    getShortString: function(point) {
+        var resolution = this.getResolution();
+        var x = (point.x / resolution + this.left);
+        var y = (this.top - point.y / resolution);
+
+        if (this.inValidRange(x, y)) { 
+            return x + "," + y;
+        } else {
+            return false;
+        }
+    },
+    
+    /**
+     * Method: getPosition
+     * Finds the position of an svg node.
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * 
+     * Returns:
+     * {Object} hash with x and y properties, representing the coordinates
+     *     within the svg coordinate system
+     */
+    getPosition: function(node) {
+        return({
+            x: parseFloat(node.getAttributeNS(null, "cx")),
+            y: parseFloat(node.getAttributeNS(null, "cy"))
+        });
+    },
+
+    /**
+     * Method: importSymbol
+     * add a new symbol definition from the rendererer's symbol hash
+     * 
+     * Parameters:
+     * graphicName - {String} name of the symbol to import
+     * 
+     * Returns:
+     * {String} - id of the imported symbol
+     */      
+    importSymbol: function (graphicName)  {
+        if (!this.defs) {
+            // create svg defs tag
+            this.defs = this.createDefs();
+        }
+        var id = this.container.id + "-" + graphicName;
+        
+        // check if symbol already exists in the defs
+        if (document.getElementById(id) != null) {
+            return id;
+        }
+        
+        var symbol = OpenLayers.Renderer.symbol[graphicName];
+        if (!symbol) {
+            throw new Error(graphicName + ' is not a valid symbol name');
+            return;
+        }
+
+        var symbolNode = this.nodeFactory(id, "symbol");
+        var node = this.nodeFactory(null, "polygon");
+        symbolNode.appendChild(node);
+        var symbolExtent = new OpenLayers.Bounds(
+                                    Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+
+        var points = "";
+        var x,y;
+        for (var i=0; i<symbol.length; i=i+2) {
+            x = symbol[i];
+            y = symbol[i+1];
+            symbolExtent.left = Math.min(symbolExtent.left, x);
+            symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+            symbolExtent.right = Math.max(symbolExtent.right, x);
+            symbolExtent.top = Math.max(symbolExtent.top, y);
+            points += " " + x + "," + y;
+        }
+        
+        node.setAttributeNS(null, "points", points);
+        
+        var width = symbolExtent.getWidth();
+        var height = symbolExtent.getHeight();
+        // create a viewBox three times as large as the symbol itself,
+        // to allow for strokeWidth being displayed correctly at the corners.
+        var viewBox = [symbolExtent.left - width,
+                        symbolExtent.bottom - height, width * 3, height * 3];
+        symbolNode.setAttributeNS(null, "viewBox", viewBox.join(" "));
+        this.symbolSize[id] = Math.max(width, height) * 3;
+        
+        this.defs.appendChild(symbolNode);
+        return symbolNode.id;
+    },
+
+    CLASS_NAME: "OpenLayers.Renderer.SVG"
+});
+/* ======================================================================
+    OpenLayers/Renderer/VML.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Renderer/Elements.js
+ */
+
+/**
+ * Class: OpenLayers.Renderer.VML
+ * Render vector features in browsers with VML capability.  Construct a new
+ * VML renderer with the <OpenLayers.Renderer.VML> constructor.
+ * 
+ * Note that for all calculations in this class, we use toFixed() to round a 
+ * float value to an integer. This is done because it seems that VML doesn't 
+ * support float values.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Renderer.Elements>
+ */
+OpenLayers.Renderer.VML = OpenLayers.Class(OpenLayers.Renderer.Elements, {
+
+    /**
+     * Property: xmlns
+     * {String} XML Namespace URN
+     */
+    xmlns: "urn:schemas-microsoft-com:vml",
+    
+    /**
+     * Property: symbolCache
+     * {DOMElement} node holding symbols. This hash is keyed by symbol name,
+     *     and each value is a hash with a "path" and an "extent" property.
+     */
+    symbolCache: {},
+
+    /**
+     * Constructor: OpenLayers.Renderer.VML
+     * Create a new VML renderer.
+     *
+     * Parameters:
+     * containerID - {String} The id for the element that contains the renderer
+     */
+    initialize: function(containerID) {
+        if (!this.supported()) { 
+            return; 
+        }
+        if (!document.namespaces.olv) {
+            document.namespaces.add("olv", this.xmlns);
+            var style = document.createStyleSheet();
+            style.addRule('olv\\:*', "behavior: url(#default#VML); " +
+                                   "position: absolute; display: inline-block;");
+        }
+        OpenLayers.Renderer.Elements.prototype.initialize.apply(this, 
+                                                                arguments);
+    },
+
+    /**
+     * APIMethod: destroy
+     * Deconstruct the renderer.
+     */
+    destroy: function() {
+        OpenLayers.Renderer.Elements.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * APIMethod: supported
+     * Determine whether a browser supports this renderer.
+     *
+     * Returns:
+     * {Boolean} The browser supports the VML renderer
+     */
+    supported: function() {
+        return !!(document.namespaces);
+    },    
+
+    /**
+     * Method: setExtent
+     * Set the renderer's extent
+     *
+     * Parameters:
+     * extent - {<OpenLayers.Bounds>}
+     * resolutionChanged - {Boolean}
+     * 
+     * Returns:
+     * {Boolean} true to notify the layer that the new extent does not exceed
+     *     the coordinate range, and the features will not need to be redrawn.
+     */
+    setExtent: function(extent, resolutionChanged) {
+        OpenLayers.Renderer.Elements.prototype.setExtent.apply(this, 
+                                                               arguments);
+        var resolution = this.getResolution();
+    
+        var org = extent.left/resolution + " " + 
+                    (extent.top/resolution - this.size.h);
+        this.root.setAttribute("coordorigin", org);
+
+        var size = this.size.w + " " + this.size.h;
+        this.root.setAttribute("coordsize", size);
+        
+        // flip the VML display Y axis upside down so it 
+        // matches the display Y axis of the map
+        this.root.style.flip = "y";
+        
+        return true;
+    },
+
+
+    /**
+     * Method: setSize
+     * Set the size of the drawing surface
+     *
+     * Parameters:
+     * size - {<OpenLayers.Size>} the size of the drawing surface
+     */
+    setSize: function(size) {
+        OpenLayers.Renderer.prototype.setSize.apply(this, arguments);
+
+        this.rendererRoot.style.width = this.size.w + "px";
+        this.rendererRoot.style.height = this.size.h + "px";
+
+        this.root.style.width = this.size.w + "px";
+        this.root.style.height = this.size.h + "px";
+    },
+
+    /**
+     * Method: getNodeType
+     * Get the node type for a geometry and style
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * style - {Object}
+     *
+     * Returns:
+     * {String} The corresponding node type for the specified geometry
+     */
+    getNodeType: function(geometry, style) {
+        var nodeType = null;
+        switch (geometry.CLASS_NAME) {
+            case "OpenLayers.Geometry.Point":
+                if (style.externalGraphic) {
+                    nodeType = "olv:rect";
+                } else if (this.isComplexSymbol(style.graphicName)) {
+                    nodeType = "olv:shape";
+                } else {
+                    nodeType = "olv:oval";
+                }
+                break;
+            case "OpenLayers.Geometry.Rectangle":
+                nodeType = "olv:rect";
+                break;
+            case "OpenLayers.Geometry.LineString":
+            case "OpenLayers.Geometry.LinearRing":
+            case "OpenLayers.Geometry.Polygon":
+            case "OpenLayers.Geometry.Curve":
+            case "OpenLayers.Geometry.Surface":
+                nodeType = "olv:shape";
+                break;
+            default:
+                break;
+        }
+        return nodeType;
+    },
+
+    /**
+     * Method: setStyle
+     * Use to set all the style attributes to a VML node.
+     *
+     * Parameters:
+     * node - {DOMElement} An VML element to decorate
+     * style - {Object}
+     * options - {Object} Currently supported options include 
+     *                              'isFilled' {Boolean} and
+     *                              'isStroked' {Boolean}
+     * geometry - {<OpenLayers.Geometry>}
+     */
+    setStyle: function(node, style, options, geometry) {
+        style = style  || node._style;
+        options = options || node._options;
+        var widthFactor = 1;
+        
+        if (node._geometryClass == "OpenLayers.Geometry.Point") {
+            if (style.externalGraphic) {
+                var width = style.graphicWidth || style.graphicHeight;
+                var height = style.graphicHeight || style.graphicWidth;
+                width = width ? width : style.pointRadius*2;
+                height = height ? height : style.pointRadius*2;
+
+                var resolution = this.getResolution();
+                var xOffset = (style.graphicXOffset != undefined) ?
+                    style.graphicXOffset : -(0.5 * width);
+                var yOffset = (style.graphicYOffset != undefined) ?
+                    style.graphicYOffset : -(0.5 * height);
+                
+                node.style.left = ((geometry.x/resolution)+xOffset).toFixed();
+                node.style.top = ((geometry.y/resolution)-(yOffset+height)).toFixed();
+                node.style.width = width + "px";
+                node.style.height = height + "px";
+                node.style.flip = "y";
+                
+                // modify style/options for fill and stroke styling below
+                style.fillColor = "none";
+                options.isStroked = false;
+            } else if (this.isComplexSymbol(style.graphicName)) {
+                var cache = this.importSymbol(style.graphicName);
+                var symbolExtent = cache.extent;
+                var width = symbolExtent.getWidth();
+                var height = symbolExtent.getHeight();
+                node.setAttribute("path", cache.path);
+                node.setAttribute("coordorigin", symbolExtent.left + "," +
+                                                                symbolExtent.bottom);
+                node.setAttribute("coordsize", width + "," + height);
+                node.style.left = symbolExtent.left + "px";
+                node.style.top = symbolExtent.bottom + "px";
+                node.style.width = width + "px";
+                node.style.height = height + "px";
+        
+                this.drawCircle(node, geometry, style.pointRadius);
+                node.style.flip = "y";
+            } else {
+                this.drawCircle(node, geometry, style.pointRadius);
+            }
+        }
+
+        // fill 
+        if (options.isFilled) { 
+            node.setAttribute("fillcolor", style.fillColor); 
+        } else { 
+            node.setAttribute("filled", "false"); 
+        }
+        var fills = node.getElementsByTagName("fill");
+        var fill = (fills.length == 0) ? null : fills[0];
+        if (!options.isFilled) {
+            if (fill) {
+                node.removeChild(fill);
+            }
+        } else {
+            if (!fill) {
+                fill = this.createNode('olv:fill', node.id + "_fill");
+            }
+            fill.setAttribute("opacity", style.fillOpacity);
+
+            if (node._geometryClass == "OpenLayers.Geometry.Point" &&
+                    style.externalGraphic) {
+
+                // override fillOpacity
+                if (style.graphicOpacity) {
+                    fill.setAttribute("opacity", style.graphicOpacity);
+                }
+                
+                fill.setAttribute("src", style.externalGraphic);
+                fill.setAttribute("type", "frame");
+                
+                if (!(style.graphicWidth && style.graphicHeight)) {
+                  fill.aspect = "atmost";
+                }                
+            }
+            if (fill.parentNode != node) {
+                node.appendChild(fill);
+            }
+        }
+
+        // additional rendering for rotated graphics or symbols
+        if (typeof style.rotation != "undefined") {
+            if (style.externalGraphic) {
+                this.graphicRotate(node, xOffset, yOffset);
+                // make the fill fully transparent, because we now have
+                // the graphic as imagedata element. We cannot just remove
+                // the fill, because this is part of the hack described
+                // in graphicRotate
+                fill.setAttribute("opacity", 0);
+            } else {
+                node.style.rotation = style.rotation;
+            }
+        }
+
+        // stroke 
+        if (options.isStroked) { 
+            node.setAttribute("strokecolor", style.strokeColor); 
+            node.setAttribute("strokeweight", style.strokeWidth + "px"); 
+        } else { 
+            node.setAttribute("stroked", "false"); 
+        }
+        var strokes = node.getElementsByTagName("stroke");
+        var stroke = (strokes.length == 0) ? null : strokes[0];
+        if (!options.isStroked) {
+            if (stroke) {
+                node.removeChild(stroke);
+            }
+        } else {
+            if (!stroke) {
+                stroke = this.createNode('olv:stroke', node.id + "_stroke");
+                node.appendChild(stroke);
+            }
+            stroke.setAttribute("opacity", style.strokeOpacity);
+            stroke.setAttribute("endcap", !style.strokeLinecap || style.strokeLinecap == 'butt' ? 'flat' : style.strokeLinecap);
+            stroke.setAttribute("dashstyle", this.dashStyle(style));
+        }
+        
+        if (style.cursor != "inherit" && style.cursor != null) {
+            node.style.cursor = style.cursor;
+        }
+        return node;
+    },
+
+    /**
+     * Method: graphicRotate
+     * If a point is to be styled with externalGraphic and rotation, VML fills
+     * cannot be used to display the graphic, because rotation of graphic
+     * fills is not supported by the VML implementation of Internet Explorer.
+     * This method creates a olv:imagedata element inside the VML node,
+     * DXImageTransform.Matrix and BasicImage filters for rotation and
+     * opacity, and a 3-step hack to remove rendering artefacts from the
+     * graphic and preserve the ability of graphics to trigger events.
+     * Finally, OpenLayers methods are used to determine the correct
+     * insertion point of the rotated image, because DXImageTransform.Matrix
+     * does the rotation without the ability to specify a rotation center
+     * point.
+     * 
+     * Parameters:
+     * node    - {DOMElement}
+     * xOffset - {Number} rotation center relative to image, x coordinate
+     * yOffset - {Number} rotation center relative to image, y coordinate
+     */
+    graphicRotate: function(node, xOffset, yOffset) {
+        var style = style || node._style;
+        var options = node._options;
+        
+        var aspectRatio, size;
+        if (!(style.graphicWidth && style.graphicHeight)) {
+            // load the image to determine its size
+            var img = new Image();
+            img.onreadystatechange = OpenLayers.Function.bind(function() {
+                if(img.readyState == "complete" ||
+                        img.readyState == "interactive") {
+                    aspectRatio = img.width / img.height;
+                    size = Math.max(style.pointRadius * 2, 
+                        style.graphicWidth || 0,
+                        style.graphicHeight || 0);
+                    xOffset = xOffset * aspectRatio;
+                    style.graphicWidth = size * aspectRatio;
+                    style.graphicHeight = size;
+                    this.graphicRotate(node, xOffset, yOffset)
+                }
+            }, this);
+            img.src = style.externalGraphic;
+            
+            // will be called again by the onreadystate handler
+            return;
+        } else {
+            size = Math.max(style.graphicWidth, style.graphicHeight);
+            aspectRatio = style.graphicWidth / style.graphicHeight;
+        }
+        
+        var width = Math.round(style.graphicWidth || size * aspectRatio);
+        var height = Math.round(style.graphicHeight || size);
+        node.style.width = width + "px";
+        node.style.height = height + "px";
+        
+        // Three steps are required to remove artefacts for images with
+        // transparent backgrounds (resulting from using DXImageTransform
+        // filters on svg objects), while preserving awareness for browser
+        // events on images:
+        // - Use the fill as usual (like for unrotated images) to handle
+        //   events
+        // - specify an imagedata element with the same src as the fill
+        // - style the imagedata element with an AlphaImageLoader filter
+        //   with empty src
+        var image = document.getElementById(node.id + "_image");
+        if (!image) {
+            image = this.createNode("olv:imagedata", node.id + "_image");
+            node.appendChild(image);
+        }
+        image.style.width = width + "px";
+        image.style.height = height + "px";
+        image.src = style.externalGraphic;
+        image.style.filter =
+            "progid:DXImageTransform.Microsoft.AlphaImageLoader(" + 
+            "src='', sizingMethod='scale')";
+
+        var rotation = style.rotation * Math.PI / 180;
+        var sintheta = Math.sin(rotation);
+        var costheta = Math.cos(rotation);
+
+        // do the rotation on the image
+        var filter =
+            "progid:DXImageTransform.Microsoft.Matrix(M11=" + costheta +
+            ",M12=" + (-sintheta) + ",M21=" + sintheta + ",M22=" + costheta +
+            ",SizingMethod='auto expand')\n"
+
+        // set the opacity (needed for the imagedata)
+        var opacity = style.graphicOpacity || style.fillOpacity;
+        if (opacity && opacity != 1) {
+            filter += 
+                "progid:DXImageTransform.Microsoft.BasicImage(opacity=" + 
+                opacity+")\n";
+        }
+        node.style.filter = filter;
+
+        // do the rotation again on a box, so we know the insertion point
+        var centerPoint = new OpenLayers.Geometry.Point(-xOffset, -yOffset);
+        var imgBox = new OpenLayers.Bounds(0, 0, width, height).toGeometry();
+        imgBox.rotate(style.rotation, centerPoint);
+        var imgBounds = imgBox.getBounds();
+
+        node.style.left = Math.round(
+            parseInt(node.style.left) + imgBounds.left) + "px";
+        node.style.top = Math.round(
+            parseInt(node.style.top) - imgBounds.bottom) + "px";
+    },
+
+    /**
+     * Method: postDraw
+     * Some versions of Internet Explorer seem to be unable to set fillcolor
+     * and strokecolor to "none" correctly before the fill node is appended to
+     * a visible vml node. This method takes care of that and sets fillcolor
+     * and strokecolor again if needed.
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     */
+    postDraw: function(node) {
+        var fillColor = node._style.fillColor;
+        var strokeColor = node._style.strokeColor;
+        if (fillColor == "none" &&
+                node.getAttribute("fillcolor") != fillColor) {
+            node.setAttribute("fillcolor", fillColor);
+        }
+        if (strokeColor == "none" &&
+                node.getAttribute("strokecolor") != strokeColor) {
+            node.setAttribute("strokecolor", strokeColor);
+        }
+    },
+
+
+    /**
+     * Method: setNodeDimension
+     * Get the geometry's bounds, convert it to our vml coordinate system, 
+     * then set the node's position, size, and local coordinate system.
+     *   
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     */
+    setNodeDimension: function(node, geometry) {
+
+        var bbox = geometry.getBounds();
+        if(bbox) {
+            var resolution = this.getResolution();
+        
+            var scaledBox = 
+                new OpenLayers.Bounds((bbox.left/resolution).toFixed(),
+                                      (bbox.bottom/resolution).toFixed(),
+                                      (bbox.right/resolution).toFixed(),
+                                      (bbox.top/resolution).toFixed());
+            
+            // Set the internal coordinate system to draw the path
+            node.style.left = scaledBox.left + "px";
+            node.style.top = scaledBox.top + "px";
+            node.style.width = scaledBox.getWidth() + "px";
+            node.style.height = scaledBox.getHeight() + "px";
+    
+            node.coordorigin = scaledBox.left + " " + scaledBox.top;
+            node.coordsize = scaledBox.getWidth()+ " " + scaledBox.getHeight();
+        }
+    },
+    
+    /** 
+     * Method: dashStyle
+     * 
+     * Parameters:
+     * style - {Object}
+     * 
+     * Returns:
+     * {String} A VML compliant 'stroke-dasharray' value
+     */
+    dashStyle: function(style) {
+        var dash = style.strokeDashstyle;
+        switch (dash) {
+            case 'solid':
+            case 'dot':
+            case 'dash':
+            case 'dashdot':
+            case 'longdash':
+            case 'longdashdot':
+                return dash;
+            default:
+                // very basic guessing of dash style patterns
+                var parts = dash.split(/[ ,]/);
+                if (parts.length == 2) {
+                    if (1*parts[0] >= 2*parts[1]) {
+                        return "longdash";
+                    }
+                    return (parts[0] == 1 || parts[1] == 1) ? "dot" : "dash";
+                } else if (parts.length == 4) {
+                    return (1*parts[0] >= 2*parts[1]) ? "longdashdot" :
+                        "dashdot"
+                }
+                return "solid";
+        }
+    },
+
+    /**
+     * Method: createNode
+     * Create a new node
+     *
+     * Parameters:
+     * type - {String} Kind of node to draw
+     * id - {String} Id for node
+     *
+     * Returns:
+     * {DOMElement} A new node of the given type and id
+     */
+    createNode: function(type, id) {
+        var node = document.createElement(type);
+        if (id) {
+            node.setAttribute('id', id);
+        }
+        
+        // IE hack to make elements unselectable, to prevent 'blue flash'
+        // while dragging vectors; #1410
+        node.setAttribute('unselectable', 'on', 0);
+        node.onselectstart = function() { return(false); };
+        
+        return node;    
+    },
+    
+    /**
+     * Method: nodeTypeCompare
+     * Determine whether a node is of a given type
+     *
+     * Parameters:
+     * node - {DOMElement} An VML element
+     * type - {String} Kind of node
+     *
+     * Returns:
+     * {Boolean} Whether or not the specified node is of the specified type
+     */
+    nodeTypeCompare: function(node, type) {
+
+        //split type
+        var subType = type;
+        var splitIndex = subType.indexOf(":");
+        if (splitIndex != -1) {
+            subType = subType.substr(splitIndex+1);
+        }
+
+        //split nodeName
+        var nodeName = node.nodeName;
+        splitIndex = nodeName.indexOf(":");
+        if (splitIndex != -1) {
+            nodeName = nodeName.substr(splitIndex+1);
+        }
+
+        return (subType == nodeName);
+    },
+
+    /**
+     * Method: createRenderRoot
+     * Create the renderer root
+     *
+     * Returns:
+     * {DOMElement} The specific render engine's root element
+     */
+    createRenderRoot: function() {
+        return this.nodeFactory(this.container.id + "_vmlRoot", "div");
+    },
+
+    /**
+     * Method: createRoot
+     * Create the main root element
+     *
+     * Returns:
+     * {DOMElement} The main root element to which we'll add vectors
+     */
+    createRoot: function() {
+        return this.nodeFactory(this.container.id + "_root", "olv:group");
+    },
+    
+    /**************************************
+     *                                    *
+     *     GEOMETRY DRAWING FUNCTIONS     *
+     *                                    *
+     **************************************/
+    
+    /**
+     * Method: drawPoint
+     * Render a point
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement} or false if the point could not be drawn
+     */
+    drawPoint: function(node, geometry) {
+        return this.drawCircle(node, geometry, 1);
+    },
+
+    /**
+     * Method: drawCircle
+     * Render a circle.
+     * Size and Center a circle given geometry (x,y center) and radius
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * radius - {float}
+     * 
+     * Returns:
+     * {DOMElement} or false if the circle could not ne drawn
+     */
+    drawCircle: function(node, geometry, radius) {
+        if(!isNaN(geometry.x)&& !isNaN(geometry.y)) {
+            var resolution = this.getResolution();
+        
+            node.style.left = ((geometry.x /resolution).toFixed() - radius) + "px";
+            node.style.top = ((geometry.y /resolution).toFixed() - radius) + "px";
+    
+            var diameter = radius * 2;
+            
+            node.style.width = diameter + "px";
+            node.style.height = diameter + "px";
+            return node;
+        }
+        return false;
+    },
+
+
+    /**
+     * Method: drawLineString
+     * Render a linestring.
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawLineString: function(node, geometry) {
+        return this.drawLine(node, geometry, false);
+    },
+
+    /**
+     * Method: drawLinearRing
+     * Render a linearring
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawLinearRing: function(node, geometry) {
+        return this.drawLine(node, geometry, true);
+    },
+
+    /**
+     * Method: DrawLine
+     * Render a line.
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * closeLine - {Boolean} Close the line? (make it a ring?)
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawLine: function(node, geometry, closeLine) {
+
+        this.setNodeDimension(node, geometry);
+
+        var resolution = this.getResolution();
+        var numComponents = geometry.components.length;
+        var parts = new Array(numComponents);
+
+        var comp, x, y;
+        for (var i = 0; i < numComponents; i++) {
+            comp = geometry.components[i];
+            x = (comp.x/resolution);
+            y = (comp.y/resolution);
+            parts[i] = " " + x.toFixed() + "," + y.toFixed() + " l ";
+        }
+        var end = (closeLine) ? " x e" : " e";
+        node.path = "m" + parts.join("") + end;
+        return node;
+    },
+
+    /**
+     * Method: drawPolygon
+     * Render a polygon
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawPolygon: function(node, geometry) {
+        this.setNodeDimension(node, geometry);
+
+        var resolution = this.getResolution();
+    
+        var path = [];
+        var linearRing, i, j, len, ilen, comp, x, y;
+        for (j = 0, len=geometry.components.length; j<len; j++) {
+            linearRing = geometry.components[j];
+
+            path.push("m");
+            for (i=0, ilen=linearRing.components.length; i<ilen; i++) {
+                comp = linearRing.components[i];
+                x = comp.x / resolution;
+                y = comp.y / resolution;
+                path.push(" " + x.toFixed() + "," + y.toFixed());
+                if (i==0) {
+                    path.push(" l");
+                }
+            }
+            path.push(" x ");
+        }
+        path.push("e");
+        node.path = path.join("");
+        return node;
+    },
+
+    /**
+     * Method: drawRectangle
+     * Render a rectangle
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawRectangle: function(node, geometry) {
+        var resolution = this.getResolution();
+    
+        node.style.left = geometry.x/resolution + "px";
+        node.style.top = geometry.y/resolution + "px";
+        node.style.width = geometry.width/resolution + "px";
+        node.style.height = geometry.height/resolution + "px";
+        
+        return node;
+    },
+
+    /**
+     * Method: drawSurface
+     * 
+     * Parameters:
+     * node - {DOMElement}
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    drawSurface: function(node, geometry) {
+
+        this.setNodeDimension(node, geometry);
+
+        var resolution = this.getResolution();
+    
+        var path = [];
+        var comp, x, y;
+        for (var i=0, len=geometry.components.length; i<len; i++) {
+            comp = geometry.components[i];
+            x = comp.x / resolution;
+            y = comp.y / resolution;
+            if ((i%3)==0 && (i/3)==0) {
+                path.push("m");
+            } else if ((i%3)==1) {
+                path.push(" c");
+            }
+            path.push(" " + x + "," + y);
+        }
+        path.push(" x e");
+
+        node.path = path.join("");
+        return node;
+    },
+    
+    /**
+     * Method: importSymbol
+     * add a new symbol definition from the rendererer's symbol hash
+     * 
+     * Parameters:
+     * graphicName - {String} name of the symbol to import
+     * 
+     * Returns:
+     * {Object} - hash of {DOMElement} "symbol" and {Number} "size"
+     */      
+    importSymbol: function (graphicName)  {
+        var id = this.container.id + "-" + graphicName;
+        
+        // check if symbol already exists in the cache
+        var cache = this.symbolCache[id];
+        if (cache) {
+            return cache;
+        }
+        
+        var symbol = OpenLayers.Renderer.symbol[graphicName];
+        if (!symbol) {
+            throw new Error(graphicName + ' is not a valid symbol name');
+            return;
+        }
+
+        var symbolExtent = new OpenLayers.Bounds(
+                                    Number.MAX_VALUE, Number.MAX_VALUE, 0, 0);
+        
+        var pathitems = ["m"];
+        for (var i=0; i<symbol.length; i=i+2) {
+            x = symbol[i];
+            y = symbol[i+1];
+            symbolExtent.left = Math.min(symbolExtent.left, x);
+            symbolExtent.bottom = Math.min(symbolExtent.bottom, y);
+            symbolExtent.right = Math.max(symbolExtent.right, x);
+            symbolExtent.top = Math.max(symbolExtent.top, y);
+
+            pathitems.push(x);
+            pathitems.push(y);
+            if (i == 0) {
+                pathitems.push("l");
+            }
+        }
+        pathitems.push("x e");
+        var path = pathitems.join(" ");
+        
+        cache = {
+            path: path,
+            extent: symbolExtent
+        };
+        this.symbolCache[id] = cache;
+        
+        return cache;
+    },
+    
+    CLASS_NAME: "OpenLayers.Renderer.VML"
+});
+/* ======================================================================
     OpenLayers/Tile/Image.js
    ====================================================================== */
 
@@ -10304,14 +20895,45 @@
      * the image will be hidden behind the frame. 
      */ 
     frame: null, 
-
     
     /**
      * Property: layerAlphaHack
      * {Boolean} True if the png alpha hack needs to be applied on the layer's div.
      */
     layerAlphaHack: null,
+    
+    /**
+     * Property: isBackBuffer
+     * {Boolean} Is this tile a back buffer tile?
+     */
+    isBackBuffer: false,
+    
+    /**
+     * Property: lastRatio
+     * {Float} Used in transition code only.  This is the previous ratio
+     *     of the back buffer tile resolution to the map resolution.  Compared
+     *     with the current ratio to determine if zooming occurred.
+     */
+    lastRatio: 1,
 
+    /**
+     * Property: isFirstDraw
+     * {Boolean} Is this the first time the tile is being drawn?
+     *     This is used to force resetBackBuffer to synchronize
+     *     the backBufferTile with the foreground tile the first time
+     *     the foreground tile loads so that if the user zooms
+     *     before the layer has fully loaded, the backBufferTile for
+     *     tiles that have been loaded can be used.
+     */
+    isFirstDraw: true,
+        
+    /**
+     * Property: backBufferTile
+     * {<OpenLayers.Tile>} A clone of the tile used to create transition
+     *     effects when the tile is moved or changes resolution.
+     */
+    backBufferTile: null,
+
     /** TBD 3.0 - reorder the parameters to the init function to remove 
      *             URL. the getUrl() function on the layer gets called on 
      *             each draw(), so no need to specify it here.
@@ -10353,12 +20975,22 @@
                 this.frame.removeChild(this.imgDiv);
                 this.imgDiv.map = null;
             }
+            this.imgDiv.urls = null;
         }
         this.imgDiv = null;
         if ((this.frame != null) && (this.frame.parentNode == this.layer.div)) { 
             this.layer.div.removeChild(this.frame); 
         }
         this.frame = null; 
+        
+        /* clean up the backBufferTile if it exists */
+        if (this.backBufferTile) {
+            this.backBufferTile.destroy();
+            this.backBufferTile = null;
+        }
+        
+        this.layer.events.unregister("loadend", this, this.resetBackBuffer);
+        
         OpenLayers.Tile.prototype.destroy.apply(this, arguments);
     },
 
@@ -10401,8 +21033,47 @@
         if (this.layer != this.layer.map.baseLayer && this.layer.reproject) {
             this.bounds = this.getBoundsFromBaseLayer(this.position);
         }
-        if (!OpenLayers.Tile.prototype.draw.apply(this, arguments)) {
-            return false;    
+        var drawTile = OpenLayers.Tile.prototype.draw.apply(this, arguments);
+        
+        if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, this.layer.transitionEffect) != -1) {
+            if (drawTile) {
+                //we use a clone of this tile to create a double buffer for visual
+                //continuity.  The backBufferTile is used to create transition
+                //effects while the tile in the grid is repositioned and redrawn
+                if (!this.backBufferTile) {
+                    this.backBufferTile = this.clone();
+                    this.backBufferTile.hide();
+                    // this is important.  It allows the backBuffer to place itself
+                    // appropriately in the DOM.  The Image subclass needs to put
+                    // the backBufferTile behind the main tile so the tiles can
+                    // load over top and display as soon as they are loaded.
+                    this.backBufferTile.isBackBuffer = true;
+                    
+                    // potentially end any transition effects when the tile loads
+                    this.events.register('loadend', this, this.resetBackBuffer);
+                    
+                    // clear transition back buffer tile only after all tiles in
+                    // this layer have loaded to avoid visual glitches
+                    this.layer.events.register("loadend", this, this.resetBackBuffer);
+                }
+                // run any transition effects
+                this.startTransition();
+            } else {
+                // if we aren't going to draw the tile, then the backBuffer should
+                // be hidden too!
+                if (this.backBufferTile) {
+                    this.backBufferTile.clear();
+                }
+            }
+        } else {
+            if (drawTile && this.isFirstDraw) {
+                this.events.register('loadend', this, this.showTile);
+                this.isFirstDraw = false;
+            }   
+        }    
+        
+        if (!drawTile) {
+            return false;
         }
         
         if (this.isLoading) {
@@ -10416,6 +21087,42 @@
         return this.renderTile();
     },
     
+    /** 
+     * Method: resetBackBuffer
+     * Triggered by two different events, layer loadend, and tile loadend.
+     *     In any of these cases, we check to see if we can hide the 
+     *     backBufferTile yet and update its parameters to match the 
+     *     foreground tile.
+     *
+     * Basic logic:
+     *  - If the backBufferTile hasn't been drawn yet, reset it
+     *  - If layer is still loading, show foreground tile but don't hide
+     *    the backBufferTile yet
+     *  - If layer is done loading, reset backBuffer tile and show 
+     *    foreground tile
+     */
+    resetBackBuffer: function() {
+        this.showTile();
+        if (this.backBufferTile && 
+            (this.isFirstDraw || !this.layer.numLoadingTiles)) {
+            this.isFirstDraw = false;
+            // check to see if the backBufferTile is within the max extents
+            // before rendering it 
+            var maxExtent = this.layer.maxExtent;
+            var withinMaxExtent = (maxExtent &&
+                                   this.bounds.intersectsBounds(maxExtent, false));
+            if (withinMaxExtent) {
+                this.backBufferTile.position = this.position;
+                this.backBufferTile.bounds = this.bounds;
+                this.backBufferTile.size = this.size;
+                this.backBufferTile.imageSize = this.layer.imageSize || this.size;
+                this.backBufferTile.imageOffset = this.layer.imageOffset;
+                this.backBufferTile.resolution = this.layer.getResolution();
+                this.backBufferTile.renderTile();
+            }
+        }
+    },
+    
     /**
      * Method: renderTile
      * Internal function to actually initialize the image tile,
@@ -10428,6 +21135,11 @@
 
         this.imgDiv.viewRequestID = this.layer.map.viewRequestID;
         
+        // needed for changing to a different serve for onload error
+        if (this.layer.url instanceof Array) {
+            this.imgDiv.urls = this.layer.url.slice();
+        }
+        
         this.url = this.layer.getURL(this.bounds);
         // position the frame 
         OpenLayers.Util.modifyDOMElement(this.frame, 
@@ -10685,6 +21397,201 @@
     OpenLayers.Util.getBrowserName() == "safari" || 
     OpenLayers.Util.getBrowserName() == "opera"); 
 /* ======================================================================
+    OpenLayers/Tile/WFS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+ 
+/**
+ * @requires OpenLayers/Tile.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Tile.WFS
+ * Instances of OpenLayers.Tile.WFS are used to manage the image tiles
+ * used by various layers.  Create a new image tile with the
+ * <OpenLayers.Tile.WFS> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Tile>
+ */
+OpenLayers.Tile.WFS = OpenLayers.Class(OpenLayers.Tile, {
+
+    /** 
+     * Property: features 
+     * {Array(<OpenLayers.Feature>)} list of features in this tile 
+     */
+    features: null,
+
+    /** 
+     * Property: url 
+     * {String} 
+     */
+    url: null,
+    
+    /** 
+     * Property: request 
+     * {<OpenLayers.Request.XMLHttpRequest>} 
+     */ 
+    request: null,     
+    
+    /** TBD 3.0 - reorder the parameters to the init function to put URL 
+     *             as last, so we can continue to call tile.initialize() 
+     *             without changing the arguments. 
+     * 
+     * Constructor: OpenLayers.Tile.WFS
+     * Constructor for a new <OpenLayers.Tile.WFS> instance.
+     * 
+     * Parameters:
+     * layer - {<OpenLayers.Layer>} layer that the tile will go in.
+     * position - {<OpenLayers.Pixel>}
+     * bounds - {<OpenLayers.Bounds>}
+     * url - {<String>}
+     * size - {<OpenLayers.Size>}
+     */   
+    initialize: function(layer, position, bounds, url, size) {
+        OpenLayers.Tile.prototype.initialize.apply(this, arguments);
+        this.url = url;        
+        this.features = [];
+    },
+
+    /** 
+     * APIMethod: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+        OpenLayers.Tile.prototype.destroy.apply(this, arguments);
+        this.destroyAllFeatures();
+        this.features = null;
+        this.url = null;
+        if(this.request) {
+            this.request.abort();
+            //this.request.destroy();
+            this.request = null;
+        }
+    },
+
+    /** 
+     * Method: clear
+     *  Clear the tile of any bounds/position-related data so that it can 
+     *   be reused in a new location.
+     */
+    clear: function() {
+        this.destroyAllFeatures();
+    },
+    
+    /**
+     * Method: draw
+     * Check that a tile should be drawn, and load features for it.
+     */
+    draw:function() {
+        if (OpenLayers.Tile.prototype.draw.apply(this, arguments)) {
+            if (this.isLoading) {
+                //if already loading, send 'reload' instead of 'loadstart'.
+                this.events.triggerEvent("reload"); 
+            } else {
+                this.isLoading = true;
+                this.events.triggerEvent("loadstart");
+            }
+            this.loadFeaturesForRegion(this.requestSuccess);
+        }
+    },
+
+    /** 
+    * Method: loadFeaturesForRegion
+    * Abort any pending requests and issue another request for data. 
+    *
+    * Input are function pointers for what to do on success and failure.
+    *
+    * Parameters:
+    * success - {function}
+    * failure - {function}
+    */
+    loadFeaturesForRegion:function(success, failure) {
+        if(this.request) {
+            this.request.abort();
+        }
+        this.request = OpenLayers.Request.GET({
+            url: this.url,
+            success: success,
+            failure: failure,
+            scope: this
+        });
+    },
+    
+    /**
+    * Method: requestSuccess
+    * Called on return from request succcess. Adds results via 
+    * layer.addFeatures in vector mode, addResults otherwise. 
+    *
+    * Parameters:
+    * request - {<OpenLayers.Request.XMLHttpRequest>}
+    */
+    requestSuccess:function(request) {
+        if (this.features) {
+            var doc = request.responseXML;
+            if (!doc || !doc.documentElement) {
+                doc = request.responseText; 
+            }
+            if (this.layer.vectorMode) {
+                this.layer.addFeatures(this.layer.formatObject.read(doc));
+            } else {
+                var xml = new OpenLayers.Format.XML();
+                if (typeof doc == "string") {
+                    doc = xml.read(doc);
+                }
+                var resultFeatures = xml.getElementsByTagNameNS(
+                    doc, "http://www.opengis.net/gml", "featureMember"
+                );
+                this.addResults(resultFeatures);
+            }
+        }
+        if (this.events) {
+            this.events.triggerEvent("loadend"); 
+        }
+
+        //request produced with success, we can delete the request object.
+        //this.request.destroy();
+        this.request = null;
+    },
+
+    /**
+     * Method: addResults
+     * Construct new feature via layer featureClass constructor, and add to
+     * this.features.
+     * 
+     * Parameters:
+     * results - {Object}
+     */
+    addResults: function(results) {
+        for (var i=0; i < results.length; i++) {
+            var feature = new this.layer.featureClass(this.layer, 
+                                                      results[i]);
+            this.features.push(feature);
+        }
+    },
+
+
+    /** 
+     * Method: destroyAllFeatures
+     * Iterate through and call destroy() on each feature, removing it from
+     *   the local array
+     */
+    destroyAllFeatures: function() {
+        while(this.features.length > 0) {
+            var feature = this.features.shift();
+            feature.destroy();
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Tile.WFS"
+  }
+);
+/* ======================================================================
     OpenLayers/Control/OverviewMap.js
    ====================================================================== */
 
@@ -10778,7 +21685,7 @@
      * resolution at which to zoom farther in on the overview map.
      */
     maxRatio: 32,
-
+    
     /**
      * APIProperty: mapOptions
      * {Object} An object containing any non-default properties to be sent to
@@ -10794,6 +21701,12 @@
     handlers: null,
 
     /**
+     * Property: resolutionFactor
+     * {Object}
+     */
+    resolutionFactor: 1,
+
+    /**
      * Constructor: OpenLayers.Control.OverviewMap
      * Create a new overview map
      *
@@ -10930,7 +21843,7 @@
             
             var eventsToStop = ['dblclick','mousedown'];
             
-            for (var i = 0; i < eventsToStop.length; i++) {
+            for (var i=0, len=eventsToStop.length; i<len; i++) {
 
                 OpenLayers.Event.observe(this.maximizeDiv, 
                                          eventsToStop[i], 
@@ -11092,6 +22005,13 @@
                                 Math.max(mapExtent.bottom, maxExtent.bottom),
                                 Math.min(mapExtent.right, maxExtent.right),
                                 Math.min(mapExtent.top, maxExtent.top));        
+
+        if (this.ovmap.getProjection() != this.map.getProjection()) {
+            testExtent = testExtent.transform(
+                this.map.getProjectionObject(),
+                this.ovmap.getProjectionObject() );
+        }
+
         var resRatio = this.ovmap.getResolution() / this.map.getResolution();
         return ((resRatio > this.minRatio) &&
                 (resRatio <= this.maxRatio) &&
@@ -11113,8 +22033,16 @@
             // zoom out overview map
             targetRes = this.maxRatio * mapRes;
         }
-        this.ovmap.setCenter(this.map.center,
-                            this.ovmap.getZoomForResolution(targetRes));
+        var center;
+        if (this.ovmap.getProjection() != this.map.getProjection()) {
+            center = this.map.center.clone();
+            center.transform(this.map.getProjectionObject(),
+                this.ovmap.getProjectionObject() );
+        } else {
+            center = this.map.center;
+        }
+        this.ovmap.setCenter(center, this.ovmap.getZoomForResolution(
+            targetRes * this.resolutionFactor));
         this.updateRectToMap();
     },
     
@@ -11176,6 +22104,15 @@
             }
         });
 
+        if (this.ovmap.getProjection() != this.map.getProjection()) {
+            var sourceUnits = this.map.getProjectionObject().getUnits() ||
+                this.map.units || this.map.baseLayer.units;
+            var targetUnits = this.ovmap.getProjectionObject().getUnits() ||
+                this.ovmap.units || this.ovmap.baseLayer.units;
+            this.resolutionFactor = sourceUnits && targetUnits ?
+                OpenLayers.INCHES_PER_UNIT[sourceUnits] /
+                OpenLayers.INCHES_PER_UNIT[targetUnits] : 1;
+        }
     },
         
     /**
@@ -11183,14 +22120,16 @@
      * Updates the extent rectangle position and size to match the map extent
      */
     updateRectToMap: function() {
-        // The base layer for overview map needs to be in the same projection
-        // as the base layer for the main map.  This should be made more robust.
-        if(this.map.units != 'degrees') {
-            if(this.ovmap.getProjection() && (this.map.getProjection() != this.ovmap.getProjection())) {
-                alert(OpenLayers.i18n("sameProjection"));
-            }
+        // If the projections differ we need to reproject
+        var bounds;
+        if (this.ovmap.getProjection() != this.map.getProjection()) {
+            bounds = this.map.getExtent().transform(
+                this.map.getProjectionObject(), 
+                this.ovmap.getProjectionObject() );
+        } else {
+            bounds = this.map.getExtent();
         }
-        var pxBounds = this.getRectBoundsFromMapBounds(this.map.getExtent());
+        var pxBounds = this.getRectBoundsFromMapBounds(bounds);
         if (pxBounds) {
             this.setRectPxBounds(pxBounds);
         }
@@ -11202,6 +22141,11 @@
      */
     updateMapToRect: function() {
         var lonLatBounds = this.getMapBoundsFromRectBounds(this.rectPxBounds);
+        if (this.ovmap.getProjection() != this.map.getProjection()) {
+            lonLatBounds = lonLatBounds.transform(
+                this.ovmap.getProjectionObject(),
+                this.map.getProjectionObject() );
+        }
         this.map.panTo(lonLatBounds.getCenterLonLat());
     },
 
@@ -11341,6 +22285,1306 @@
     CLASS_NAME: 'OpenLayers.Control.OverviewMap'
 });
 /* ======================================================================
+    OpenLayers/Feature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Marker.js
+ * @requires OpenLayers/Popup/AnchoredBubble.js
+ */
+
+/**
+ * Class: OpenLayers.Feature
+ * Features are combinations of geography and attributes. The OpenLayers.Feature
+ *     class specifically combines a marker and a lonlat.
+ */
+OpenLayers.Feature = OpenLayers.Class({
+
+    /** 
+     * Property: layer 
+     * {<OpenLayers.Layer>} 
+     */
+    layer: null,
+
+    /** 
+     * Property: id 
+     * {String} 
+     */
+    id: null,
+    
+    /** 
+     * Property: lonlat 
+     * {<OpenLayers.LonLat>} 
+     */
+    lonlat: null,
+
+    /** 
+     * Property: data 
+     * {Object} 
+     */
+    data: null,
+
+    /** 
+     * Property: marker 
+     * {<OpenLayers.Marker>} 
+     */
+    marker: null,
+
+    /**
+     * APIProperty: popupClass
+     * {<OpenLayers.Class>} The class which will be used to instantiate
+     *     a new Popup. Default is <OpenLayers.Popup.AnchoredBubble>.
+     */
+    popupClass: OpenLayers.Popup.AnchoredBubble,
+
+    /** 
+     * Property: popup 
+     * {<OpenLayers.Popup>} 
+     */
+    popup: null,
+
+    /** 
+     * Constructor: OpenLayers.Feature
+     * Constructor for features.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer>} 
+     * lonlat - {<OpenLayers.LonLat>} 
+     * data - {Object} 
+     * 
+     * Returns:
+     * {<OpenLayers.Feature>}
+     */
+    initialize: function(layer, lonlat, data) {
+        this.layer = layer;
+        this.lonlat = lonlat;
+        this.data = (data != null) ? data : {};
+        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); 
+    },
+
+    /** 
+     * Method: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+
+        //remove the popup from the map
+        if ((this.layer != null) && (this.layer.map != null)) {
+            if (this.popup != null) {
+                this.layer.map.removePopup(this.popup);
+            }
+        }
+
+        this.layer = null;
+        this.id = null;
+        this.lonlat = null;
+        this.data = null;
+        if (this.marker != null) {
+            this.destroyMarker(this.marker);
+            this.marker = null;
+        }
+        if (this.popup != null) {
+            this.destroyPopup(this.popup);
+            this.popup = null;
+        }
+    },
+    
+    /**
+     * Method: onScreen
+     * 
+     * Returns:
+     * {Boolean} Whether or not the feature is currently visible on screen
+     *           (based on its 'lonlat' property)
+     */
+    onScreen:function() {
+        
+        var onScreen = false;
+        if ((this.layer != null) && (this.layer.map != null)) {
+            var screenBounds = this.layer.map.getExtent();
+            onScreen = screenBounds.containsLonLat(this.lonlat);
+        }    
+        return onScreen;
+    },
+    
+
+    /**
+     * Method: createMarker
+     * Based on the data associated with the Feature, create and return a marker object.
+     *
+     * Returns: 
+     * {<OpenLayers.Marker>} A Marker Object created from the 'lonlat' and 'icon' properties
+     *          set in this.data. If no 'lonlat' is set, returns null. If no
+     *          'icon' is set, OpenLayers.Marker() will load the default image.
+     *          
+     *          Note - this.marker is set to return value
+     * 
+     */
+    createMarker: function() {
+
+        if (this.lonlat != null) {
+            this.marker = new OpenLayers.Marker(this.lonlat, this.data.icon);
+        }
+        return this.marker;
+    },
+
+    /**
+     * Method: destroyMarker
+     * Destroys marker.
+     * If user overrides the createMarker() function, s/he should be able
+     *   to also specify an alternative function for destroying it
+     */
+    destroyMarker: function() {
+        this.marker.destroy();  
+    },
+
+    /**
+     * Method: createPopup
+     * Creates a popup object created from the 'lonlat', 'popupSize',
+     *     and 'popupContentHTML' properties set in this.data. It uses
+     *     this.marker.icon as default anchor. 
+     *  
+     *  If no 'lonlat' is set, returns null. 
+     *  If no this.marker has been created, no anchor is sent.
+     *
+     *  Note - the returned popup object is 'owned' by the feature, so you
+     *      cannot use the popup's destroy method to discard the popup.
+     *      Instead, you must use the feature's destroyPopup
+     * 
+     *  Note - this.popup is set to return value
+     * 
+     * Parameters: 
+     * closeBox - {Boolean} create popup with closebox or not
+     * 
+     * Returns:
+     * {<OpenLayers.Popup>} Returns the created popup, which is also set
+     *     as 'popup' property of this feature. Will be of whatever type
+     *     specified by this feature's 'popupClass' property, but must be
+     *     of type <OpenLayers.Popup>.
+     * 
+     */
+    createPopup: function(closeBox) {
+
+        if (this.lonlat != null) {
+            
+            var id = this.id + "_popup";
+            var anchor = (this.marker) ? this.marker.icon : null;
+
+            if (!this.popup) {
+                this.popup = new this.popupClass(id, 
+                                                 this.lonlat,
+                                                 this.data.popupSize,
+                                                 this.data.popupContentHTML,
+                                                 anchor, 
+                                                 closeBox); 
+            }    
+            if (this.data.overflow != null) {
+                this.popup.contentDiv.style.overflow = this.data.overflow;
+            }    
+            
+            this.popup.feature = this;
+        }        
+        return this.popup;
+    },
+
+    
+    /**
+     * Method: destroyPopup
+     * Destroys the popup created via createPopup.
+     *
+     * As with the marker, if user overrides the createPopup() function, s/he 
+     *   should also be able to override the destruction
+     */
+    destroyPopup: function() {
+        if (this.popup) {
+            this.popup.feature = null;
+            this.popup.destroy();
+            this.popup = null;
+        }    
+    },
+
+    CLASS_NAME: "OpenLayers.Feature"
+});
+/* ======================================================================
+    OpenLayers/Format/WMC.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC
+ * Read and write Web Map Context documents.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WMC = OpenLayers.Class({
+    
+    /**
+     * APIProperty: defaultVersion
+     * {String} Version number to assume if none found.  Default is "1.1.0".
+     */
+    defaultVersion: "1.1.0",
+    
+    /**
+     * APIProperty: version
+     * {String} Specify a version string if one is known.
+     */
+    version: null,
+
+    /**
+     * Property: layerOptions
+     * {Object} Default options for layers created by the parser. These
+     *     options are overridden by the options which are read from the 
+     *     capabilities document.
+     */
+    layerOptions: null, 
+    
+    /**
+     * Property: parser
+     * {Object} Instance of the versioned parser.  Cached for multiple read and
+     *     write calls of the same version.
+     */
+    parser: null,
+
+    /**
+     * Constructor: OpenLayers.Format.WMC
+     * Create a new parser for WMC docs.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Util.extend(this, options);
+        this.options = options;
+    },
+
+    /**
+     * APIMethod: read
+     * Read WMC data from a string, and return an object with map properties
+     *     and a list of layers. 
+     * 
+     * Parameters: 
+     * data - {String} or {DOMElement} data to read/parse.
+     * options - {Object} The options object must contain a map property.  If
+     *     the map property is a string, it must be the id of a dom element
+     *     where the new map will be placed.  If the map property is an
+     *     <OpenLayers.Map>, the layers from the context document will be added
+     *     to the map.
+     *
+     * Returns:
+     * {<OpenLayers.Map>} A map based on the context.
+     */
+    read: function(data, options) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var root = data.documentElement;
+        var version = this.version;
+        if(!version) {
+            version = root.getAttribute("version");
+            if(!version) {
+                version = this.defaultVersion;
+            }
+        }
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.WMC[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a WMC parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        var context = this.parser.read(data, options);
+        var map;
+        if(options.map) {
+            this.context = context;
+            if(options.map instanceof OpenLayers.Map) {
+                map = this.mergeContextToMap(context, options.map);
+            } else {
+                map = this.contextToMap(context, options.map);
+            }
+        } else {
+            // not documented as part of the API, provided as a non-API option
+            map = context;
+        }
+        return map;
+    },
+    
+    /**
+     * Method: contextToMap
+     * Create a map given a context object.
+     *
+     * Parameters:
+     * context - {Object} The context object.
+     * id - {String | Element} The dom element or element id that will contain
+     *     the map.
+     *
+     * Returns:
+     * {<OpenLayers.Map>} A map based on the context object.
+     */
+    contextToMap: function(context, id) {
+        var map = new OpenLayers.Map(id, {
+            maxExtent: context.maxExtent,
+            projection: context.projection
+        });
+        map.addLayers(context.layers);
+        map.setCenter(
+            context.bounds.getCenterLonLat(),
+            map.getZoomForExtent(context.bounds, true)
+        );
+        return map;
+    },
+    
+    /**
+     * Method: mergeContextToMap
+     * Add layers from a context object to a map.
+     *
+     * Parameters:
+     * context - {Object} The context object.
+     * map - {<OpenLayers.Map>} The map.
+     *
+     * Returns:
+     * {<OpenLayers.Map>} The same map with layers added.
+     */
+    mergeContextToMap: function(context, map) {
+        map.addLayers(context.layers);
+        return map;
+    },
+
+    /**
+     * APIMethod: write
+     * Write a WMC document given a map.
+     *
+     * Parameters:
+     * obj - {<OpenLayers.Map> | Object} A map or context object.
+     * options - {Object} Optional configuration object.
+     *
+     * Returns:
+     * {String} A WMC document string.
+     */
+    write: function(obj, options) {
+        if(obj.CLASS_NAME == "OpenLayers.Map") {
+            obj = this.mapToContext(obj);
+        }
+        var version = (options && options.version) ||
+                      this.version || this.defaultVersion;
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.WMC[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a WMS capabilities parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        var wmc = this.parser.write(obj, options);
+        return wmc;
+    },
+    
+    /**
+     * Method: mapToContext
+     * Create a context object given a map.
+     *
+     * Parameters:
+     * map - {<OpenLayers.Map>} The map.
+     *
+     * Returns:
+     * {Object} A context object.
+     */
+    mapToContext: function(map) {
+        var context = {
+            bounds: map.getExtent(),
+            maxExtent: map.maxExtent,
+            projection: map.projection,
+            layers: map.layers,
+            size: map.getSize()
+        };
+        return context;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WMC" 
+
+});
+/* ======================================================================
+    OpenLayers/Format/WMC/v1.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1
+ * Superclass for WMC version 1 parsers.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.WMC.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * Property: namespaces
+     * {Object} Mapping of namespace aliases to namespace URIs.
+     */
+    namespaces: {
+        ol: "http://openlayers.org/context",
+        wmc: "http://www.opengis.net/context",
+        sld: "http://www.opengis.net/sld",
+        xlink: "http://www.w3.org/1999/xlink",
+        xsi: "http://www.w3.org/2001/XMLSchema-instance"
+    },
+    
+    /**
+     * Property: schemaLocation
+     * {String} Schema location for a particular minor version.
+     */
+    schemaLocation: "",
+
+    /**
+     * Method: getNamespacePrefix
+     * Get the namespace prefix for a given uri from the <namespaces> object.
+     *
+     * Returns:
+     * {String} A namespace prefix or null if none found.
+     */
+    getNamespacePrefix: function(uri) {
+        var prefix = null;
+        if(uri == null) {
+            prefix = this.namespaces[this.defaultPrefix];
+        } else {
+            for(prefix in this.namespaces) {
+                if(this.namespaces[prefix] == uri) {
+                    break;
+                }
+            }
+        }
+        return prefix;
+    },
+    
+    /**
+     * Property: defaultPrefix
+     */
+    defaultPrefix: "wmc",
+
+    /**
+     * Property: rootPrefix
+     * {String} Prefix on the root node that maps to the context namespace URI.
+     */
+    rootPrefix: null,
+    
+    /**
+     * Property: defaultStyleName
+     * {String} Style name used if layer has no style param.  Default is "".
+     */
+    defaultStyleName: "",
+    
+    /**
+     * Property: defaultStyleTitle
+     * {String} Default style title.  Default is "Default".
+     */
+    defaultStyleTitle: "Default",
+    
+    /**
+     * Constructor: OpenLayers.Format.WMC.v1
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.WMC> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * Method: read
+     * Read capabilities data from a string, and return a list of layers. 
+     * 
+     * Parameters: 
+     * data - {String} or {DOMElement} data to read/parse.
+     *
+     * Returns:
+     * {Array} List of named layers.
+     */
+    read: function(data) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var root = data.documentElement;
+        this.rootPrefix = root.prefix;
+        var context = {
+            version: root.getAttribute("version")
+        };
+        this.runChildNodes(context, root);
+        return context;
+    },
+    
+    /**
+     * Method: runChildNodes
+     */
+    runChildNodes: function(obj, node) {
+        var children = node.childNodes;
+        var childNode, processor, prefix, local;
+        for(var i=0, len=children.length; i<len; ++i) {
+            childNode = children[i];
+            if(childNode.nodeType == 1) {
+                prefix = this.getNamespacePrefix(childNode.namespaceURI);
+                local = childNode.nodeName.split(":").pop();
+                processor = this["read_" + prefix + "_" + local];
+                if(processor) {
+                    processor.apply(this, [obj, childNode]);
+                }
+            }
+        }
+    },
+    
+    /**
+     * Method: read_wmc_General
+     */
+    read_wmc_General: function(context, node) {
+        this.runChildNodes(context, node);
+    },
+    
+    /**
+     * Method: read_wmc_BoundingBox
+     */
+    read_wmc_BoundingBox: function(context, node) {
+        context.projection = node.getAttribute("SRS");
+        context.bounds = new OpenLayers.Bounds(
+            parseFloat(node.getAttribute("minx")),
+            parseFloat(node.getAttribute("miny")),
+            parseFloat(node.getAttribute("maxx")),
+            parseFloat(node.getAttribute("maxy"))
+        );
+    },
+    
+    /**
+     * Method: read_wmc_LayerList
+     */
+    read_wmc_LayerList: function(context, node) {
+        context.layers = [];
+        this.runChildNodes(context, node);
+    },
+    
+    /**
+     * Method: read_wmc_Layer
+     */
+    read_wmc_Layer: function(context, node) {
+        var layerInfo = {
+            params: {},
+            options: {
+                visibility: (node.getAttribute("hidden") != "1"),
+                queryable: (node.getAttribute("queryable") == "1")
+                
+            },
+            formats: [],
+            styles: []
+        };
+        this.runChildNodes(layerInfo, node);
+        // set properties common to multiple objects on layer options/params
+        layerInfo.params.layers = layerInfo.name;
+        layerInfo.options.maxExtent = layerInfo.maxExtent;
+        // create the layer
+        var layer = this.getLayerFromInfo(layerInfo);
+        context.layers.push(layer);
+    },
+    
+    /**
+     * Method: getLayerFromInfo
+     * Create a WMS layer from a layerInfo object.
+     *
+     * Parameters:
+     * layerInfo - {Object} An object representing a WMS layer.
+     *
+     * Returns:
+     * {<OpenLayers.Layer.WMS>} A WMS layer.
+     */
+    getLayerFromInfo: function(layerInfo) {
+        var options = layerInfo.options;
+        if (this.layerOptions) {
+            OpenLayers.Util.applyDefaults(options, this.layerOptions);
+        }
+        var layer = new OpenLayers.Layer.WMS(
+            layerInfo.title,
+            layerInfo.href,
+            layerInfo.params,
+            options
+        );
+        return layer;
+    },
+    
+    /**
+     * Method: read_wmc_Extension
+     */
+    read_wmc_Extension: function(obj, node) {
+        this.runChildNodes(obj, node);
+    },
+
+    /**
+     * Method: read_ol_units
+     */
+    read_ol_units: function(layerInfo, node) {
+        layerInfo.options.units = this.getChildValue(node);
+    },
+    
+    /**
+     * Method: read_ol_maxExtent
+     */
+    read_ol_maxExtent: function(obj, node) {
+        var bounds = new OpenLayers.Bounds(
+            node.getAttribute("minx"), node.getAttribute("miny"),
+            node.getAttribute("maxx"), node.getAttribute("maxy")
+        );
+        obj.maxExtent = bounds;
+    },
+    
+    /**
+     * Method: read_ol_transparent
+     */
+    read_ol_transparent: function(layerInfo, node) {
+        layerInfo.params.transparent = this.getChildValue(node);
+    },
+
+    /**
+     * Method: read_ol_numZoomLevels
+     */
+    read_ol_numZoomLevels: function(layerInfo, node) {
+        layerInfo.options.numZoomLevels = parseInt(this.getChildValue(node));
+    },
+
+    /**
+     * Method: read_ol_opacity
+     */
+    read_ol_opacity: function(layerInfo, node) {
+        layerInfo.options.opacity = parseFloat(this.getChildValue(node));
+    },
+
+    /**
+     * Method: read_ol_singleTile
+     */
+    read_ol_singleTile: function(layerInfo, node) {
+        layerInfo.options.singleTile = (this.getChildValue(node) == "true");
+    },
+
+    /**
+     * Method: read_ol_isBaseLayer
+     */
+    read_ol_isBaseLayer: function(layerInfo, node) {
+        layerInfo.options.isBaseLayer = (this.getChildValue(node) == "true");
+    },
+
+    /**
+     * Method: read_ol_displayInLayerSwitcher
+     */
+    read_ol_displayInLayerSwitcher: function(layerInfo, node) {
+        layerInfo.options.displayInLayerSwitcher =
+            (this.getChildValue(node) == "true");
+    },
+
+    /**
+     * Method: read_wmc_Server
+     */
+    read_wmc_Server: function(layerInfo, node) {
+        layerInfo.params.version = node.getAttribute("version");
+        this.runChildNodes(layerInfo, node);
+    },
+
+    /**
+     * Method: read_wmc_FormatList
+     */
+    read_wmc_FormatList: function(layerInfo, node) {
+        this.runChildNodes(layerInfo, node);
+    },
+
+    /**
+     * Method: read_wmc_Format
+     */
+    read_wmc_Format: function(layerInfo, node) {
+        var format = this.getChildValue(node);
+        layerInfo.formats.push(format);
+        if(node.getAttribute("current") == "1") {
+            layerInfo.params.format = format;
+        }
+    },
+    
+    /**
+     * Method: read_wmc_StyleList
+     */
+    read_wmc_StyleList: function(layerInfo, node) {
+        this.runChildNodes(layerInfo, node);
+    },
+
+    /**
+     * Method: read_wmc_Style
+     */
+    read_wmc_Style: function(layerInfo, node) {
+        var style = {};
+        this.runChildNodes(style, node);
+        if(node.getAttribute("current") == "1") {
+            // three style types to consider
+            // 1) linked SLD
+            // 2) inline SLD
+            // 3) named style
+            // running child nodes always gets name, optionally gets href or body
+            if(style.href) {
+                layerInfo.params.sld = style.href;
+            } else if(style.body) {
+                layerInfo.params.sld_body = style.body;
+            } else {
+                layerInfo.params.styles = style.name;
+            }
+        }
+        layerInfo.styles.push(style);
+    },
+    
+    /**
+     * Method: read_wmc_SLD
+     */
+    read_wmc_SLD: function(style, node) {
+        this.runChildNodes(style, node);
+        // style either comes back with an href or a body property
+    },
+    
+    /**
+     * Method: read_sld_StyledLayerDescriptor
+     */
+    read_sld_StyledLayerDescriptor: function(sld, node) {
+        var xml = OpenLayers.Format.XML.prototype.write.apply(this, [node]);
+        sld.body = xml;
+    },
+
+    /**
+     * Method: read_wmc_OnlineResource
+     */
+    read_wmc_OnlineResource: function(obj, node) {
+        obj.href = this.getAttributeNS(
+            node, this.namespaces.xlink, "href"
+        );
+    },
+    
+    /**
+     * Method: read_wmc_Name
+     */
+    read_wmc_Name: function(obj, node) {
+        var name = this.getChildValue(node);
+        if(name) {
+            obj.name = name;
+        }
+    },
+
+    /**
+     * Method: read_wmc_Title
+     */
+    read_wmc_Title: function(obj, node) {
+        var title = this.getChildValue(node);
+        if(title) {
+            obj.title = title;
+        }
+    },
+
+    /**
+     * Method: read_wmc_MetadataURL
+     */
+    read_wmc_MetadataURL: function(layerInfo, node) {
+        var metadataURL = {};
+        var links = node.getElementsByTagName("OnlineResource");
+        if(links.length > 0) {
+            this.read_wmc_OnlineResource(metadataURL, links[0]);
+        }
+        layerInfo.options.metadataURL = metadataURL.href;
+
+    },
+
+    /**
+     * Method: read_wmc_Abstract
+     */
+    read_wmc_Abstract: function(obj, node) {
+        var abst = this.getChildValue(node);
+        if(abst) {
+            obj["abstract"] = abst;
+        }
+    },
+    
+    /**
+     * Method: read_wmc_LatLonBoundingBox
+     */
+    read_wmc_LatLonBoundingBox: function(layer, node) {
+        layer.llbbox = [
+            parseFloat(node.getAttribute("minx")),
+            parseFloat(node.getAttribute("miny")),
+            parseFloat(node.getAttribute("maxx")),
+            parseFloat(node.getAttribute("maxy"))
+        ];
+    },
+
+    /**
+     * Method: read_wmc_LegendURL
+     */
+    read_wmc_LegendURL: function(style, node) {
+        var legend = {
+            width: node.getAttribute('width'),
+            height: node.getAttribute('height')
+        };
+        var links = node.getElementsByTagName("OnlineResource");
+        if(links.length > 0) {
+            this.read_wmc_OnlineResource(legend, links[0]);
+        }
+        style.legend = legend;
+    },
+    
+    /**
+     * Method: write
+     *
+     * Parameters:
+     * context - {Object} An object representing the map context.
+     * options - {Object} Optional object.
+     *
+     * Returns:
+     * {String} A WMC document string.
+     */
+    write: function(context, options) {
+        var root = this.createElementDefaultNS("ViewContext");
+        this.setAttributes(root, {
+            version: this.VERSION,
+            id: (options && typeof options.id == "string") ?
+                    options.id :
+                    OpenLayers.Util.createUniqueID("OpenLayers_Context_")
+        });
+        
+        // add schemaLocation attribute
+        this.setAttributeNS(
+            root, this.namespaces.xsi,
+            "xsi:schemaLocation", this.schemaLocation
+        );
+        
+        // required General element
+        root.appendChild(this.write_wmc_General(context));
+
+        // required LayerList element
+        root.appendChild(this.write_wmc_LayerList(context));
+
+        return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+    },
+    
+    /**
+     * Method: createElementDefaultNS
+     * Shorthand for createElementNS with namespace from <defaultPrefix>.
+     *     Can optionally be used to set attributes and a text child value.
+     *
+     * Parameters:
+     * name - {String} The qualified node name.
+     * childValue - {String} Optional value for text child node.
+     * attributes - {Object} Optional object representing attributes.
+     *
+     * Returns:
+     * {Element} An element node.
+     */
+    createElementDefaultNS: function(name, childValue, attributes) {
+        var node = this.createElementNS(
+            this.namespaces[this.defaultPrefix],
+            name
+        );
+        if(childValue) {
+            node.appendChild(this.createTextNode(childValue));
+        }
+        if(attributes) {
+            this.setAttributes(node, attributes);
+        }
+        return node;
+    },
+    
+    /**
+     * Method: setAttributes
+     * Set multiple attributes given key value pairs from an object.
+     *
+     * Parameters:
+     * node - {Element} An element node.
+     * obj - {Object} An object whose properties represent attribute names and
+     *     values represent attribute values.
+     */
+    setAttributes: function(node, obj) {
+        var value;
+        for(var name in obj) {
+            value = obj[name].toString();
+            if(value.match(/[A-Z]/)) {
+                // safari lowercases attributes with setAttribute
+                this.setAttributeNS(node, null, name, value);
+            } else {
+                node.setAttribute(name, value);
+            }
+        }
+    },
+
+    /**
+     * Method: write_wmc_General
+     * Create a General node given an context object.
+     *
+     * Parameters:
+     * context - {Object} Context object.
+     *
+     * Returns:
+     * {Element} A WMC General element node.
+     */
+    write_wmc_General: function(context) {
+        var node = this.createElementDefaultNS("General");
+
+        // optional Window element
+        if(context.size) {
+            node.appendChild(this.createElementDefaultNS(
+                "Window", null,
+                {
+                    width: context.size.w,
+                    height: context.size.h
+                }
+            ));
+        }
+        
+        // required BoundingBox element
+        var bounds = context.bounds;
+        node.appendChild(this.createElementDefaultNS(
+            "BoundingBox", null,
+            {
+                minx: bounds.left.toPrecision(10),
+                miny: bounds.bottom.toPrecision(10),
+                maxx: bounds.right.toPrecision(10),
+                maxy: bounds.top.toPrecision(10),
+                SRS: context.projection
+            }
+        ));
+
+        // required Title element
+        node.appendChild(this.createElementDefaultNS(
+            "Title", context.title
+        ));
+        
+        // OpenLayers specific map properties
+        node.appendChild(this.write_ol_MapExtension(context));
+        
+        return node;
+    },
+    
+    /**
+     * Method: write_ol_MapExtension
+     */
+    write_ol_MapExtension: function(context) {
+        var node = this.createElementDefaultNS("Extension");
+        
+        var bounds = context.maxExtent;
+        if(bounds) {
+            var maxExtent = this.createElementNS(
+                this.namespaces.ol, "ol:maxExtent"
+            );
+            this.setAttributes(maxExtent, {
+                minx: bounds.left.toPrecision(10),
+                miny: bounds.bottom.toPrecision(10),
+                maxx: bounds.right.toPrecision(10),
+                maxy: bounds.top.toPrecision(10)
+            });
+            node.appendChild(maxExtent);
+        }
+        
+        return node;
+    },
+    
+    /**
+     * Method: write_wmc_LayerList
+     * Create a LayerList node given an context object.
+     *
+     * Parameters:
+     * context - {Object} Context object.
+     *
+     * Returns:
+     * {Element} A WMC LayerList element node.
+     */
+    write_wmc_LayerList: function(context) {
+        var list = this.createElementDefaultNS("LayerList");
+        
+        var layer;
+        for(var i=0, len=context.layers.length; i<len; ++i) {
+            layer = context.layers[i];
+            if(layer instanceof OpenLayers.Layer.WMS) {
+                list.appendChild(this.write_wmc_Layer(layer));
+            }
+        }
+        
+        return list;
+    },
+
+    /**
+     * Method: write_wmc_Layer
+     * Create a Layer node given a layer object.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC Layer element node.
+     */
+    write_wmc_Layer: function(layer) {
+        var node = this.createElementDefaultNS(
+            "Layer", null, {
+                queryable: layer.queryable ? "1" : "0",
+                hidden: layer.visibility ? "0" : "1"
+            }
+        );
+        
+        // required Server element
+        node.appendChild(this.write_wmc_Server(layer));
+
+        // required Name element
+        node.appendChild(this.createElementDefaultNS(
+            "Name", layer.params["LAYERS"]
+        ));
+        
+        // required Title element
+        node.appendChild(this.createElementDefaultNS(
+            "Title", layer.name
+        ));
+
+        // optional MetadataURL element
+        if (layer.metadataURL) {
+            node.appendChild(this.write_wmc_MetadataURL(layer));
+        }
+        
+        // optional FormatList element
+        node.appendChild(this.write_wmc_FormatList(layer));
+
+        // optional StyleList element
+        node.appendChild(this.write_wmc_StyleList(layer));
+        
+        // OpenLayers specific properties go in an Extension element
+        node.appendChild(this.write_wmc_LayerExtension(layer));
+
+        return node;
+    },
+    
+    /**
+     * Method: write_wmc_LayerExtension
+     * Add OpenLayers specific layer parameters to an Extension element.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} A WMS layer.
+     *
+     * Returns:
+     * {Element} A WMC Extension element (for a layer).
+     */
+    write_wmc_LayerExtension: function(layer) {
+        var node = this.createElementDefaultNS("Extension");
+        
+        var bounds = layer.maxExtent;
+        var maxExtent = this.createElementNS(
+            this.namespaces.ol, "ol:maxExtent"
+        );
+        this.setAttributes(maxExtent, {
+            minx: bounds.left.toPrecision(10),
+            miny: bounds.bottom.toPrecision(10),
+            maxx: bounds.right.toPrecision(10),
+            maxy: bounds.top.toPrecision(10)
+        });
+        node.appendChild(maxExtent);
+        
+        var param = layer.params["TRANSPARENT"];
+        if(param) {
+            var trans = this.createElementNS(
+                this.namespaces.ol, "ol:transparent"
+            );
+            trans.appendChild(this.createTextNode(param));
+            node.appendChild(trans);
+        }
+        
+        var properties = [
+            "numZoomLevels", "units", "isBaseLayer",
+            "opacity", "displayInLayerSwitcher", "singleTile"
+        ];
+        var child;
+        for(var i=0, len=properties.length; i<len; ++i) {
+            child = this.createOLPropertyNode(layer, properties[i]);
+            if(child) {
+                node.appendChild(child);
+            }
+        }
+
+        return node;
+    },
+    
+    /**
+     * Method: createOLPropertyNode
+     * Create a node representing an OpenLayers property.  If the property is
+     *     null or undefined, null will be returned.
+     *
+     * Parameters:
+     * object - {Object} An object.
+     * prop - {String} A property.
+     *
+     * Returns:
+     * {Element} A property node.
+     */
+    createOLPropertyNode: function(obj, prop) {
+        var node = null;
+        if(obj[prop] != null) {
+            node = this.createElementNS(this.namespaces.ol, "ol:" + prop);
+            node.appendChild(this.createTextNode(obj[prop].toString()));
+        }
+        return node;
+    },
+
+    /**
+     * Method: write_wmc_Server
+     * Create a Server node given a layer object.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC Server element node.
+     */
+    write_wmc_Server: function(layer) {
+        var node = this.createElementDefaultNS("Server");
+        this.setAttributes(node, {
+            service: "OGC:WMS",
+            version: layer.params["VERSION"]
+        });
+        
+        // required OnlineResource element
+        node.appendChild(this.write_wmc_OnlineResource(layer.url));
+        
+        return node;
+    },
+
+    /**
+     * Method: write_wmc_MetadataURL
+     * Create a MetadataURL node given a layer object.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC metadataURL element node.
+     */
+    write_wmc_MetadataURL: function(layer) {
+        var node = this.createElementDefaultNS("MetadataURL");
+
+        // required OnlineResource element
+        node.appendChild(this.write_wmc_OnlineResource(layer.metadataURL));
+
+        return node;
+    },
+
+    /**
+     * Method: write_wmc_FormatList
+     * Create a FormatList node given a layer.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC FormatList element node.
+     */
+    write_wmc_FormatList: function(layer) {
+        var node = this.createElementDefaultNS("FormatList");
+        node.appendChild(this.createElementDefaultNS(
+            "Format", layer.params["FORMAT"], {current: "1"}
+        ));
+
+        return node;
+    },
+
+    /**
+     * Method: write_wmc_StyleList
+     * Create a StyleList node given a layer.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC StyleList element node.
+     */
+    write_wmc_StyleList: function(layer) {
+        var node = this.createElementDefaultNS("StyleList");
+        var style = this.createElementDefaultNS(
+            "Style", null, {current: "1"}
+        );
+        
+        // Style can come from one of three places (prioritized as below):
+        // 1) an SLD parameter
+        // 2) and SLD_BODY parameter
+        // 3) the STYLES parameter
+        
+        if(layer.params["SLD"]) {
+            // create link from SLD parameter
+            var sld = this.createElementDefaultNS("SLD");
+            var link = this.write_wmc_OnlineResource(layer.params["SLD"]);
+            sld.appendChild(link);
+            style.appendChild(sld);
+        } else if(layer.params["SLD_BODY"]) {
+            // include sld fragment from SLD_BODY parameter
+            var sld = this.createElementDefaultNS("SLD");
+            var body = layer.params["SLD_BODY"];
+            // read in body as xml doc - assume proper namespace declarations
+            var doc = OpenLayers.Format.XML.prototype.read.apply(this, [body]);
+            // append to StyledLayerDescriptor node
+            var imported = doc.documentElement;
+            if(sld.ownerDocument && sld.ownerDocument.importNode) {
+                imported = sld.ownerDocument.importNode(imported, true);
+            }
+            sld.appendChild(imported);
+            style.appendChild(sld);            
+        } else {
+            // use name(s) from STYLES parameter
+            var name = layer.params["STYLES"] ?
+                layer.params["STYLES"] : this.defaultStyleName;
+            
+            style.appendChild(this.createElementDefaultNS("Name", name));
+            style.appendChild(this.createElementDefaultNS(
+                "Title", this.defaultStyleTitle
+            ));
+        }
+        node.appendChild(style);
+        return node;
+    },
+
+    /**
+     * Method: write_wmc_OnlineResource
+     * Create an OnlineResource node given a URL.
+     *
+     * Parameters:
+     * href - {String} URL for the resource.
+     *
+     * Returns:
+     * {Element} A WMC OnlineResource element node.
+     */
+    write_wmc_OnlineResource: function(href) {
+        var node = this.createElementDefaultNS("OnlineResource");
+        this.setAttributeNS(node, this.namespaces.xlink, "xlink:type", "simple");
+        this.setAttributeNS(node, this.namespaces.xlink, "xlink:href", href);
+        return node;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WMC.v1" 
+
+});
+/* ======================================================================
     OpenLayers/Handler/Click.js
    ====================================================================== */
 
@@ -11436,6 +23680,13 @@
     down: null,
     
     /**
+     * Property: rightclickTimerId
+     * {Number} The id of the right mouse timeout waiting to clear the 
+     *     <delayedEvent>.
+     */
+    rightclickTimerId: null,
+    
+    /**
      * Constructor: OpenLayers.Handler.Click
      * Create a new click handler.
      * 
@@ -11471,8 +23722,80 @@
      * {Boolean} Continue propagating this event.
      */
     mousedown: null,
+
+    /**
+     * Method: mouseup
+     * Handle mouseup.  Installed to support collection of right mouse events.
+     * 
+     * Returns:
+     * {Boolean} Continue propagating this event.
+     */
+    mouseup:  function (evt) {
+        var propagate = true;
+
+        // Collect right mouse clicks from the mouseup
+        //  IE - ignores the second right click in mousedown so using
+        //  mouseup instead
+        if (this.checkModifiers(evt) && 
+            this.control.handleRightClicks && 
+            OpenLayers.Event.isRightClick(evt)) {
+          propogate = this.rightclick(evt);
+        }
+
+        return propagate;
+    },
     
     /**
+     * Method: rightclick
+     * Handle rightclick.  For a dblrightclick, we get two clicks so we need 
+     *     to always register for dblrightclick to properly handle single 
+     *     clicks.
+     *     
+     * Returns:
+     * {Boolean} Continue propagating this event.
+     */
+    rightclick: function(evt) {
+        if(this.passesTolerance(evt)) {
+           if(this.rightclickTimerId != null) {
+                //Second click received before timeout this must be 
+                // a double click
+                this.clearTimer();      
+                this.callback('dblrightclick', [evt]);
+                return !this.stopDouble;
+            } else { 
+                //Set the rightclickTimerId, send evt only if double is 
+                // true else trigger single
+                var clickEvent = this['double'] ?
+                    OpenLayers.Util.extend({}, evt) : 
+                    this.callback('rightclick', [evt]);
+
+                var delayedRightCall = OpenLayers.Function.bind(
+                    this.delayedRightCall, 
+                    this, 
+                    clickEvent
+                );
+                this.rightclickTimerId = window.setTimeout(
+                    delayedRightCall, this.delay
+                );
+            } 
+        }
+        return !this.stopSingle;
+    },
+    
+    /**
+     * Method: delayedRightCall
+     * Sets <rightclickTimerId> to null.  And optionally triggers the 
+     *     rightclick callback if evt is set.
+     */
+    delayedRightCall: function(evt) {
+        this.rightclickTimerId = null;
+        if (evt) {
+           this.callback('rightclick', [evt]);
+        }
+        return !this.stopSingle;
+    },
+    
+    /**
      * Method: dblclick
      * Handle dblclick.  For a dblclick, we get two clicks in some browsers
      *     (FF) and one in others (IE).  So we need to always register for
@@ -11657,6 +23980,23 @@
      * {Function}
      */
     oldOnselectstart: null,
+    
+    /**
+     * Property: interval
+     * {Integer} In order to increase performance, an interval (in 
+     *     milliseconds) can be set to reduce the number of drag events 
+     *     called. If set, a new drag event will not be set until the 
+     *     interval has passed. 
+     *     Defaults to 0, meaning no interval. 
+     */
+    interval: 0,
+    
+    /**
+     * Property: timeoutId
+     * {String} The id of the timeout used for the mousedown interval.
+     *     This is "private", and should be left alone.
+     */
+    timeoutId: null,
 
     /**
      * Constructor: OpenLayers.Handler.Drag
@@ -11782,20 +24122,29 @@
      * {Boolean} Let the event propagate.
      */
     mousemove: function (evt) {
-        if (this.started) {
-            if(evt.xy.x != this.last.x || evt.xy.y != this.last.y) {
-                this.dragging = true;
-                this.move(evt);
-                this.callback("move", [evt.xy]);
-                if(!this.oldOnselectstart) {
-                    this.oldOnselectstart = document.onselectstart;
-                    document.onselectstart = function() {return false;};
-                }
-                this.last = evt.xy;
+        if (this.started && !this.timeoutId && (evt.xy.x != this.last.x || evt.xy.y != this.last.y)) {
+            if (this.interval > 0) {
+                this.timeoutId = setTimeout(OpenLayers.Function.bind(this.removeTimeout, this), this.interval);
             }
+            this.dragging = true;
+            this.move(evt);
+            this.callback("move", [evt.xy]);
+            if(!this.oldOnselectstart) {
+                this.oldOnselectstart = document.onselectstart;
+                document.onselectstart = function() {return false;};
+            }
+            this.last = this.evt.xy;
         }
         return true;
     },
+    
+    /**
+     * Method: removeTimeout
+     * Private. Called by mousemove() to remove the drag timeout.
+     */
+    removeTimeout: function() {
+        this.timeoutId = null;
+    },
 
     /**
      * Method: mouseup
@@ -11908,6 +24257,680 @@
     CLASS_NAME: "OpenLayers.Handler.Drag"
 });
 /* ======================================================================
+    OpenLayers/Handler/Feature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Feature 
+ * Handler to respond to mouse events related to a drawn feature.  Callbacks
+ *     with the following keys will be notified of the following events
+ *     associated with features: click, clickout, over, out, and dblclick.
+ *
+ * This handler stops event propagation for mousedown and mouseup if those
+ *     browser events target features that can be selected.
+ */
+OpenLayers.Handler.Feature = OpenLayers.Class(OpenLayers.Handler, {
+
+    /**
+     * Property: EVENTMAP
+     * {Object} A object mapping the browser events to objects with callback
+     *     keys for in and out.
+     */
+    EVENTMAP: {
+        'click': {'in': 'click', 'out': 'clickout'},
+        'mousemove': {'in': 'over', 'out': 'out'},
+        'dblclick': {'in': 'dblclick', 'out': null},
+        'mousedown': {'in': null, 'out': null},
+        'mouseup': {'in': null, 'out': null}
+    },
+
+    /**
+     * Property: feature
+     * {<OpenLayers.Feature.Vector>} The last feature that was hovered.
+     */
+    feature: null,
+
+    /**
+     * Property: lastFeature
+     * {<OpenLayers.Feature.Vector>} The last feature that was handled.
+     */
+    lastFeature: null,
+
+    /**
+     * Property: down
+     * {<OpenLayers.Pixel>} The location of the last mousedown.
+     */
+    down: null,
+
+    /**
+     * Property: up
+     * {<OpenLayers.Pixel>} The location of the last mouseup.
+     */
+    up: null,
+    
+    /**
+     * Property: clickoutTolerance
+     * {Number} The number of pixels the mouse can move during a click that
+     *     still constitutes a click out.  When dragging the map, clicks should
+     *     not trigger the clickout property unless this tolerance is reached.
+     *     Default is 4.
+     */
+    clickoutTolerance: 4,
+
+    /**
+     * Property: geometryTypes
+     * To restrict dragging to a limited set of geometry types, send a list
+     * of strings corresponding to the geometry class names.
+     * 
+     * @type Array(String)
+     */
+    geometryTypes: null,
+
+    /**
+     * Property: stopClick
+     * {Boolean} If stopClick is set to true, handled clicks do not
+     *      propagate to other click listeners. Otherwise, handled clicks
+     *      do propagate. Unhandled clicks always propagate, whatever the
+     *      value of stopClick. Defaults to true.
+     */
+    stopClick: true,
+
+    /**
+     * Property: stopDown
+     * {Boolean} If stopDown is set to true, handled mousedowns do not
+     *      propagate to other mousedown listeners. Otherwise, handled
+     *      mousedowns do propagate. Unhandled mousedowns always propagate,
+     *      whatever the value of stopDown. Defaults to true.
+     */
+    stopDown: true,
+
+    /**
+     * Property: stopUp
+     * {Boolean} If stopUp is set to true, handled mouseups do not
+     *      propagate to other mouseup listeners. Otherwise, handled mouseups
+     *      do propagate. Unhandled mouseups always propagate, whatever the
+     *      value of stopUp. Defaults to false.
+     */
+    stopUp: false,
+    
+    /**
+     * Constructor: OpenLayers.Handler.Feature
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} 
+     * layers - {Array(<OpenLayers.Layer.Vector>)}
+     * callbacks - {Object} An object with a 'over' property whos value is
+     *     a function to be called when the mouse is over a feature. The 
+     *     callback should expect to recieve a single argument, the feature.
+     * options - {Object} 
+     */
+    initialize: function(control, layer, callbacks, options) {
+        OpenLayers.Handler.prototype.initialize.apply(this, [control, callbacks, options]);
+        this.layer = layer;
+    },
+
+
+    /**
+     * Method: mousedown
+     * Handle mouse down.  Stop propagation if a feature is targeted by this
+     *     event (stops map dragging during feature selection).
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    mousedown: function(evt) {
+        this.down = evt.xy;
+        return this.handle(evt) ? !this.stopDown : true;
+    },
+    
+    /**
+     * Method: mouseup
+     * Handle mouse up.  Stop propagation if a feature is targeted by this
+     *     event.
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    mouseup: function(evt) {
+        this.up = evt.xy;
+        return this.handle(evt) ? !this.stopUp : true;
+    },
+
+    /**
+     * Method: click
+     * Handle click.  Call the "click" callback if click on a feature,
+     *     or the "clickout" callback if click outside any feature.
+     * 
+     * Parameters:
+     * evt - {Event} 
+     *
+     * Returns:
+     * {Boolean}
+     */
+    click: function(evt) {
+        return this.handle(evt) ? !this.stopClick : true;
+    },
+        
+    /**
+     * Method: mousemove
+     * Handle mouse moves.  Call the "over" callback if moving in to a feature,
+     *     or the "out" callback if moving out of a feature.
+     * 
+     * Parameters:
+     * evt - {Event} 
+     *
+     * Returns:
+     * {Boolean}
+     */
+    mousemove: function(evt) {
+        if (!this.callbacks['over'] && !this.callbacks['out']) {
+            return true;
+        }     
+        this.handle(evt);
+        return true;
+    },
+    
+    /**
+     * Method: dblclick
+     * Handle dblclick.  Call the "dblclick" callback if dblclick on a feature.
+     *
+     * Parameters:
+     * evt - {Event} 
+     *
+     * Returns:
+     * {Boolean}
+     */
+    dblclick: function(evt) {
+        return !this.handle(evt);
+    },
+
+    /**
+     * Method: geometryTypeMatches
+     * Return true if the geometry type of the passed feature matches
+     *     one of the geometry types in the geometryTypes array.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Vector.Feature>}
+     *
+     * Returns:
+     * {Boolean}
+     */
+    geometryTypeMatches: function(feature) {
+        return this.geometryTypes == null ||
+            OpenLayers.Util.indexOf(this.geometryTypes,
+                                    feature.geometry.CLASS_NAME) > -1;
+    },
+
+    /**
+     * Method: handle
+     *
+     * Parameters:
+     * evt - {Event}
+     *
+     * Returns:
+     * {Boolean} The event occurred over a relevant feature.
+     */
+    handle: function(evt) {
+        var type = evt.type;
+        var handled = false;
+        var previouslyIn = !!(this.feature); // previously in a feature
+        var click = (type == "click" || type == "dblclick");
+        this.feature = this.layer.getFeatureFromEvent(evt);
+        if(this.feature) {
+            var inNew = (this.feature != this.lastFeature);
+            if(this.geometryTypeMatches(this.feature)) {
+                // in to a feature
+                if(previouslyIn && inNew) {
+                    // out of last feature and in to another
+                    this.triggerCallback(type, 'out', [this.lastFeature]);
+                    this.triggerCallback(type, 'in', [this.feature]);
+                } else if(!previouslyIn || click) {
+                    // in feature for the first time
+                    this.triggerCallback(type, 'in', [this.feature]);
+                }
+                this.lastFeature = this.feature;
+                handled = true;
+            } else {
+                // not in to a feature
+                if(previouslyIn && inNew || (click && this.lastFeature)) {
+                    // out of last feature for the first time
+                    this.triggerCallback(type, 'out', [this.lastFeature]);
+                }
+                // next time the mouse goes in a feature whose geometry type
+                // doesn't match we don't want to call the 'out' callback
+                // again, so let's set this.feature to null so that
+                // previouslyIn will evaluate to false the next time
+                // we enter handle. Yes, a bit hackish...
+                this.feature = null;
+            }
+        } else {
+            if(previouslyIn || (click && this.lastFeature)) {
+                this.triggerCallback(type, 'out', [this.lastFeature]);
+            }
+        }
+        return handled;
+    },
+    
+    /**
+     * Method: triggerCallback
+     * Call the callback keyed in the event map with the supplied arguments.
+     *     For click out, the <clickoutTolerance> is checked first.
+     *
+     * Parameters:
+     * type - {String}
+     */
+    triggerCallback: function(type, mode, args) {
+        var key = this.EVENTMAP[type][mode];
+        if(key) {
+            if(type == 'click' && mode == 'out' && this.up && this.down) {
+                // for clickout, only trigger callback if tolerance is met
+                var dpx = Math.sqrt(
+                    Math.pow(this.up.x - this.down.x, 2) +
+                    Math.pow(this.up.y - this.down.y, 2)
+                );
+                if(dpx <= this.clickoutTolerance) {
+                    this.callback(key, args);
+                }
+            } else {
+                this.callback(key, args);
+            }
+        }
+    },
+
+    /**
+     * Method: activate 
+     * Turn on the handler.  Returns false if the handler was already active.
+     *
+     * Returns:
+     * {Boolean}
+     */
+    activate: function() {
+        var activated = false;
+        if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+            this.moveLayerToTop();
+            this.map.events.on({
+                "removelayer": this.handleMapEvents,
+                "changelayer": this.handleMapEvents,
+                scope: this
+            });
+            activated = true;
+        }
+        return activated;
+    },
+    
+    /**
+     * Method: deactivate 
+     * Turn off the handler.  Returns false if the handler was already active.
+     *
+     * Returns: 
+     * {Boolean}
+     */
+    deactivate: function() {
+        var deactivated = false;
+        if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+            this.moveLayerBack();
+            this.feature = null;
+            this.lastFeature = null;
+            this.down = null;
+            this.up = null;
+            this.map.events.un({
+                "removelayer": this.handleMapEvents,
+                "changelayer": this.handleMapEvents,
+                scope: this
+            });
+            deactivated = true;
+        }
+        return deactivated;
+    },
+    
+    /**
+     * Method handleMapEvents
+     * 
+     * Parameters:
+     * evt - {Object}
+     */
+    handleMapEvents: function(evt) {
+        if (!evt.property || evt.property == "order") {
+            this.moveLayerToTop();
+        }
+    },
+    
+    /**
+     * Method: moveLayerToTop
+     * Moves the layer for this handler to the top, so mouse events can reach
+     * it.
+     */
+    moveLayerToTop: function() {
+        var index = Math.max(this.map.Z_INDEX_BASE['Feature'] - 1,
+            this.layer.getZIndex()) + 1;
+        this.layer.setZIndex(index);
+        
+    },
+    
+    /**
+     * Method: moveLayerBack
+     * Moves the layer back to the position determined by the map's layers
+     * array.
+     */
+    moveLayerBack: function() {
+        var index = this.layer.getZIndex() - 1;
+        if (index >= this.map.Z_INDEX_BASE['Feature']) {
+            this.layer.setZIndex(index);
+        } else {
+            this.map.setLayerZIndex(this.layer,
+                this.map.getLayerIndex(this.layer));
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Feature"
+});
+/* ======================================================================
+    OpenLayers/Handler/Hover.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the clear BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Hover
+ * The hover handler is to be used to emulate mouseovers on objects
+ *      on the map that aren't DOM elements. For example one can use
+ *      this handler to send WMS/GetFeatureInfo requests as the user
+ *      moves the mouve over the map.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Handler> 
+ */
+OpenLayers.Handler.Hover = OpenLayers.Class(OpenLayers.Handler, {
+
+    /**
+     * APIProperty: delay
+     * {Integer} - Number of milliseconds between mousemoves before
+     *      the event is considered a hover. Default is 500.
+     */
+    delay: 500,
+    
+    /**
+     * APIProperty: pixelTolerance
+     * {Integer} - Maximum number of pixels between mousemoves for
+     *      an event to be considered a hover. Default is null.
+     */
+    pixelTolerance: null,
+
+    /**
+     * APIProperty: stopMove
+     * {Boolean} Stop other listeners from being notified on mousemoves.
+     *      Default is false.
+     */
+    stopMove: false,
+
+    /**
+     * Property: px
+     * {<OpenLayers.Pixel>} The location of the last mousemove, expressed
+     *      in pixels.
+     */
+    px: null,
+
+    /**
+     * Property: timerId
+     * {Number} The id of the timer.
+     */
+    timerId: null,
+ 
+    /**
+     * Constructor: OpenLayers.Handler.Hover
+     * Construct a hover handler.
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} The control that initialized this
+     *     handler.  The control is assumed to have a valid map property; that
+     *     map is used in the handler's own setMap method.
+     * callbacks - {Object} An object whose properties correspond to abstracted
+     *     events or sequences of browser events.  The values for these
+     *     properties are functions defined by the control that get called by
+     *     the handler.
+     * options - {Object} An optional object whose properties will be set on
+     *     the handler.
+     */
+    initialize: function(control, callbacks, options) {
+        OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+    },
+
+    /**
+     * Method: mousemove
+     * Called when the mouse moves on the map.
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>}
+     *
+     * Returns:
+     * {Boolean} Continue propagating this event.
+     */
+    mousemove: function(evt) {
+        if(this.passesTolerance(evt.xy)) {
+            this.clearTimer();
+            this.callback('move', [evt]);
+            this.px = evt.xy;
+            // clone the evt so original properties can be accessed even
+            // if the browser deletes them during the delay
+            evt = OpenLayers.Util.extend({}, evt);
+            this.timerId = window.setTimeout(
+                OpenLayers.Function.bind(this.delayedCall, this, evt),
+                this.delay
+            );
+        }
+        return !this.stopMove;
+    },
+
+    /**
+     * Method: mouseout
+     * Called when the mouse goes out of the map.
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>}
+     *
+     * Returns:
+     * {Boolean} Continue propagating this event.
+     */
+    mouseout: function(evt) {
+        if (OpenLayers.Util.mouseLeft(evt, this.map.div)) {
+            this.clearTimer();
+            this.callback('move', [evt]);
+        }
+        return true;
+    },
+
+    /**
+     * Method: passesTolerance
+     * Determine whether the mouse move is within the optional pixel tolerance.
+     *
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     *
+     * Returns:
+     * {Boolean} The mouse move is within the pixel tolerance.
+     */
+    passesTolerance: function(px) {
+        var passes = true;
+        if(this.pixelTolerance && this.px) {
+            var dpx = Math.sqrt(
+                Math.pow(this.px.x - px.x, 2) +
+                Math.pow(this.px.y - px.y, 2)
+            );
+            if(dpx < this.pixelTolerance) {
+                passes = false;
+            }
+        }
+        return passes;
+    },
+
+    /**
+     * Method: clearTimer
+     * Clear the timer and set <timerId> to null.
+     */
+    clearTimer: function() {
+        if(this.timerId != null) {
+            window.clearTimeout(this.timerId);
+            this.timerId = null;
+        }
+    },
+
+    /**
+     * Method: delayedCall
+     * Triggers pause callback.
+     *
+     * Parameters:
+     * evt - {<OpenLayers.Event>}
+     */
+    delayedCall: function(evt) {
+        this.callback('pause', [evt]);
+    },
+
+    /**
+     * APIMethod: deactivate
+     * Deactivate the handler.
+     *
+     * Returns:
+     * {Boolean} The handler was successfully deactivated.
+     */
+    deactivate: function() {
+        var deactivated = false;
+        if(OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+            this.clearTimer();
+            deactivated = true;
+        }
+        return deactivated;
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Hover"
+});
+/* ======================================================================
+    OpenLayers/Handler/Keyboard.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.handler.Keyboard
+ * A handler for keyboard events.  Create a new instance with the
+ *     <OpenLayers.Handler.Keyboard> constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Handler> 
+ */
+OpenLayers.Handler.Keyboard = OpenLayers.Class(OpenLayers.Handler, {
+
+    /* http://www.quirksmode.org/js/keys.html explains key x-browser
+        key handling quirks in pretty nice detail */
+
+    /** 
+     * Constant: KEY_EVENTS
+     * keydown, keypress, keyup
+     */
+    KEY_EVENTS: ["keydown", "keyup"],
+
+    /** 
+    * Property: eventListener
+    * {Function}
+    */
+    eventListener: null,
+
+    /**
+     * Constructor: OpenLayers.Handler.Keyboard
+     * Returns a new keyboard handler.
+     * 
+     * Parameters:
+     * control - {<OpenLayers.Control>} The control that is making use of
+     *     this handler.  If a handler is being used without a control, the
+     *     handlers setMap method must be overridden to deal properly with
+     *     the map.
+     * callbacks - {Object} An object containing a single function to be
+     *     called when the drag operation is finished. The callback should
+     *     expect to recieve a single argument, the pixel location of the event.
+     *     Callbacks for 'keydown', 'keypress', and 'keyup' are supported.
+     * options - {Object} Optional object whose properties will be set on the
+     *     handler.
+     */
+    initialize: function(control, callbacks, options) {
+        OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+        // cache the bound event listener method so it can be unobserved later
+        this.eventListener = OpenLayers.Function.bindAsEventListener(
+            this.handleKeyEvent, this
+        );
+    },
+    
+    /**
+     * Method: destroy
+     */
+    destroy: function() {
+        this.deactivate();
+        this.eventListener = null;
+        OpenLayers.Handler.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * Method: activate
+     */
+    activate: function() {
+        if (OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+            for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+                OpenLayers.Event.observe(
+                    document, this.KEY_EVENTS[i], this.eventListener);
+            }
+            return true;
+        } else {
+            return false;
+        }
+    },
+
+    /**
+     * Method: deactivate
+     */
+    deactivate: function() {
+        var deactivated = false;
+        if (OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+            for (var i=0, len=this.KEY_EVENTS.length; i<len; i++) {
+                OpenLayers.Event.stopObserving(
+                    document, this.KEY_EVENTS[i], this.eventListener);
+            }
+            deactivated = true;
+        }
+        return deactivated;
+    },
+
+    /**
+     * Method: handleKeyEvent 
+     */
+    handleKeyEvent: function (evt) {
+        if (this.checkModifiers(evt)) {
+            this.callback(evt.type, [evt]);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Keyboard"
+});
+/* ======================================================================
     OpenLayers/Handler/MouseWheel.js
    ====================================================================== */
 
@@ -12016,7 +25039,7 @@
             }
 
             if (!overLayerDiv) {
-                for(var i=0; i < this.map.layers.length; i++) {
+                for(var i=0, len=this.map.layers.length; i<len; i++) {
                     // Are we in the layer div? Note that we have two cases
                     // here: one is to catch EventPane layers, which have a 
                     // pane above the layer (layer.pane)
@@ -12197,6 +25220,23 @@
     opacity: null,
 
     /**
+     * APIProperty: alwaysInRange
+     * {Boolean} If a layer's display should not be scale-based, this should 
+     *     be set to true. This will cause the layer, as an overlay, to always 
+     *     be 'active', by always returning true from the calculateInRange() 
+     *     function. 
+     * 
+     *     If not explicitly specified for a layer, its value will be 
+     *     determined on startup in initResolutions() based on whether or not 
+     *     any scale-specific properties have been set as options on the 
+     *     layer. If no scale-specific options have been set on the layer, we 
+     *     assume that it should always be in range.
+     * 
+     *     See #987 for more info.
+     */
+    alwaysInRange: null,   
+
+    /**
      * Constant: EVENT_TYPES
      * {Array(String)} Supported application event types.  Register a listener
      *     for a particular event with the following syntax:
@@ -12216,8 +25256,12 @@
      *  - *loadend* Triggered when layer loading ends.
      *  - *loadcancel* Triggered when layer loading is canceled.
      *  - *visibilitychanged* Triggered when layer visibility is changed.
+     *  - *moveend* Triggered when layer is moved, object passed as
+     *      argument has a zoomChanged boolean property which tells that the
+     *      zoom has changed.
      */
-    EVENT_TYPES: ["loadstart", "loadend", "loadcancel", "visibilitychanged"],
+    EVENT_TYPES: ["loadstart", "loadend", "loadcancel", "visibilitychanged",
+                  "moveend"],
         
     /**
      * APIProperty: events
@@ -12774,14 +25818,20 @@
      * 
      * Returns:
      * {Boolean} The layer is displayable at the current map's current
-     *     resolution.
+     *     resolution. Note that if 'alwaysInRange' is true for the layer, 
+     *     this function will always return true.
      */
     calculateInRange: function() {
         var inRange = false;
-        if (this.map) {
-            var resolution = this.map.getResolution();
-            inRange = ( (resolution >= this.minResolution) &&
-                        (resolution <= this.maxResolution) );
+
+        if (this.alwaysInRange) {
+            inRange = true;
+        } else {
+            if (this.map) {
+                var resolution = this.map.getResolution();
+                inRange = ( (resolution >= this.minResolution) &&
+                            (resolution <= this.maxResolution) );
+            }
         }
         return inRange;
     },
@@ -12836,27 +25886,60 @@
           'numZoomLevels', 'maxZoomLevel'
         );
 
+        //these are the properties which do *not* imply that user wishes 
+        // this layer to be scale-dependant
+        var notScaleProps = ['projection', 'units'];    
+
+        //should the layer be scale-dependant? default is false -- this will 
+        // only be set true if we find that the user has specified a property
+        // from the 'props' array that is not in 'notScaleProps'
+        var useInRange = false;
+
         // First we create a new object where we will store all of the 
         //  resolution-related properties that we find in either the layer's
         //  'options' array or from the map.
         //
         var confProps = {};        
-        for(var i=0; i < props.length; i++) {
+        for(var i=0, len=props.length; i<len; i++) {
             var property = props[i];
+            
+            // If the layer had one of these properties set *and* it is 
+            // a scale property (is not a non-scale property), then we assume
+            // the user did intend to use scale-dependant display (useInRange).
+            if (this.options[property] && 
+                OpenLayers.Util.indexOf(notScaleProps, property) == -1) {
+                useInRange = true;
+            }
+                   
             confProps[property] = this.options[property] || this.map[property];
         }
 
-        // Do not use the scales/resolutions at the map level if
-        // minScale/minResolution and maxScale/maxResolution are
-        // specified at the layer level
-        if (this.options.minScale != null &&
-            this.options.maxScale != null &&
+        //only automatically set 'alwaysInRange' if the user hasn't already 
+        // set it (to true or false, since the default is null). If user did
+        // not intend to use scale-dependant display then we set they layer
+        // as alwaysInRange. This means calculateInRange() will always return 
+        // true and the layer will never be turned off due to scale changes.
+        //
+        if (this.alwaysInRange == null) {
+            this.alwaysInRange = !useInRange;
+        }
+
+        // Do not use the scales array set at the map level if 
+        // either minScale or maxScale or both are set at the
+        // layer level
+        if ((this.options.minScale != null ||
+             this.options.maxScale != null) &&
             this.options.scales == null) {
+
             confProps.scales = null;
         }
-        if (this.options.minResolution != null &&
-            this.options.maxResolution != null &&
+        // Do not use the resolutions array set at the map level if 
+        // either minResolution or maxResolution or both are set at the
+        // layer level
+        if ((this.options.minResolution != null ||
+             this.options.maxResolution != null) &&
             this.options.resolutions == null) {
+
             confProps.resolutions = null;
         }
 
@@ -12874,7 +25957,7 @@
           //preset levels
             if (confProps.scales != null) {
                 confProps.resolutions = [];
-                for(var i = 0; i < confProps.scales.length; i++) {
+                for(var i=0, len=confProps.scales.length; i<len; i++) {
                     var scale = confProps.scales[i];
                     confProps.resolutions[i] = 
                        OpenLayers.Util.getResolutionFromScale(scale, 
@@ -12960,7 +26043,7 @@
         this.minResolution = confProps.resolutions[lastIndex];
         
         this.scales = [];
-        for(var i = 0; i < confProps.resolutions.length; i++) {
+        for(var i=0, len=confProps.resolutions.length; i<len; i++) {
             this.scales[i] = 
                OpenLayers.Util.getScaleFromResolution(confProps.resolutions[i], 
                                                       confProps.units);
@@ -13083,7 +26166,7 @@
             var highRes = this.resolutions[lowZoom];
             var lowRes = this.resolutions[highZoom];
             var res;
-            for(var i=0; i<this.resolutions.length; ++i) {
+            for(var i=0, len=this.resolutions.length; i<len; ++i) {
                 res = this.resolutions[i];
                 if(res >= resolution) {
                     highRes = res;
@@ -13104,7 +26187,7 @@
         } else {
             var diff;
             var minDiff = Number.POSITIVE_INFINITY;
-            for(var i=0; i < this.resolutions.length; i++) {            
+            for(var i=0, len=this.resolutions.length; i<len; i++) {            
                 if (closest) {
                     diff = Math.abs(this.resolutions[i] - resolution);
                     if (diff > minDiff) {
@@ -13189,7 +26272,7 @@
     setOpacity: function(opacity) {
         if (opacity != this.opacity) {
             this.opacity = opacity;
-            for(var i=0; i<this.div.childNodes.length; ++i) {
+            for(var i=0, len=this.div.childNodes.length; i<len; ++i) {
                 var element = this.div.childNodes[i].firstChild;
                 OpenLayers.Util.modifyDOMElement(element, null, null, null, 
                                                  null, null, null, opacity);
@@ -13198,6 +26281,16 @@
     },
 
     /**
+     * Method: getZIndex
+     * 
+     * Returns: 
+     * {Integer} the z-index of this layer
+     */    
+    getZIndex: function () {
+        return this.div.style.zIndex;
+    },
+
+    /**
      * Method: setZIndex
      * 
      * Parameters: 
@@ -13367,6 +26460,542 @@
 });
 
 /* ======================================================================
+    OpenLayers/Popup/FramedCloud.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Popup/Framed.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Popup.FramedCloud
+ * 
+ * Inherits from: 
+ *  - <OpenLayers.Popup.Framed>
+ */
+OpenLayers.Popup.FramedCloud = 
+  OpenLayers.Class(OpenLayers.Popup.Framed, {
+
+    /** 
+     * Property: contentDisplayClass
+     * {String} The CSS class of the popup content div.
+     */
+    contentDisplayClass: "olFramedCloudPopupContent",
+
+    /**
+     * APIProperty: autoSize
+     * {Boolean} Framed Cloud is autosizing by default.
+     */
+    autoSize: true,
+
+    /**
+     * APIProperty: panMapIfOutOfView
+     * {Boolean} Framed Cloud does pan into view by default.
+     */
+    panMapIfOutOfView: true,
+
+    /**
+     * APIProperty: imageSize
+     * {<OpenLayers.Size>}
+     */
+    imageSize: new OpenLayers.Size(676, 736),
+
+    /**
+     * APIProperty: isAlphaImage
+     * {Boolean} The FramedCloud does not use an alpha image (in honor of the 
+     *     good ie6 folk out there)
+     */
+    isAlphaImage: false,
+
+    /** 
+     * APIProperty: fixedRelativePosition
+     * {Boolean} The Framed Cloud popup works in just one fixed position.
+     */
+    fixedRelativePosition: false,
+
+    /**
+     * Property: positionBlocks
+     * {Object} Hash of differen position blocks, keyed by relativePosition
+     *     two-character code string (ie "tl", "tr", "bl", "br")
+     */
+    positionBlocks: {
+        "tl": {
+            'offset': new OpenLayers.Pixel(44, 0),
+            'padding': new OpenLayers.Bounds(8, 40, 8, 9),
+            'blocks': [
+                { // top-left
+                    size: new OpenLayers.Size('auto', 'auto'),
+                    anchor: new OpenLayers.Bounds(0, 51, 22, 0),
+                    position: new OpenLayers.Pixel(0, 0)
+                },
+                { //top-right
+                    size: new OpenLayers.Size(22, 'auto'),
+                    anchor: new OpenLayers.Bounds(null, 50, 0, 0),
+                    position: new OpenLayers.Pixel(-638, 0)
+                },
+                { //bottom-left
+                    size: new OpenLayers.Size('auto', 21),
+                    anchor: new OpenLayers.Bounds(0, 32, 80, null),
+                    position: new OpenLayers.Pixel(0, -629)
+                },
+                { //bottom-right
+                    size: new OpenLayers.Size(22, 21),
+                    anchor: new OpenLayers.Bounds(null, 32, 0, null),
+                    position: new OpenLayers.Pixel(-638, -629)
+                },
+                { // stem
+                    size: new OpenLayers.Size(81, 54),
+                    anchor: new OpenLayers.Bounds(null, 0, 0, null),
+                    position: new OpenLayers.Pixel(0, -668)
+                }
+            ]
+        },
+        "tr": {
+            'offset': new OpenLayers.Pixel(-45, 0),
+            'padding': new OpenLayers.Bounds(8, 40, 8, 9),
+            'blocks': [
+                { // top-left
+                    size: new OpenLayers.Size('auto', 'auto'),
+                    anchor: new OpenLayers.Bounds(0, 51, 22, 0),
+                    position: new OpenLayers.Pixel(0, 0)
+                },
+                { //top-right
+                    size: new OpenLayers.Size(22, 'auto'),
+                    anchor: new OpenLayers.Bounds(null, 50, 0, 0),
+                    position: new OpenLayers.Pixel(-638, 0)
+                },
+                { //bottom-left
+                    size: new OpenLayers.Size('auto', 21),
+                    anchor: new OpenLayers.Bounds(0, 32, 22, null),
+                    position: new OpenLayers.Pixel(0, -629)
+                },
+                { //bottom-right
+                    size: new OpenLayers.Size(22, 21),
+                    anchor: new OpenLayers.Bounds(null, 32, 0, null),
+                    position: new OpenLayers.Pixel(-638, -629)
+                },
+                { // stem
+                    size: new OpenLayers.Size(81, 54),
+                    anchor: new OpenLayers.Bounds(0, 0, null, null),
+                    position: new OpenLayers.Pixel(-215, -668)
+                }
+            ]
+        },
+        "bl": {
+            'offset': new OpenLayers.Pixel(45, 0),
+            'padding': new OpenLayers.Bounds(8, 9, 8, 40),
+            'blocks': [
+                { // top-left
+                    size: new OpenLayers.Size('auto', 'auto'),
+                    anchor: new OpenLayers.Bounds(0, 21, 22, 32),
+                    position: new OpenLayers.Pixel(0, 0)
+                },
+                { //top-right
+                    size: new OpenLayers.Size(22, 'auto'),
+                    anchor: new OpenLayers.Bounds(null, 21, 0, 32),
+                    position: new OpenLayers.Pixel(-638, 0)
+                },
+                { //bottom-left
+                    size: new OpenLayers.Size('auto', 21),
+                    anchor: new OpenLayers.Bounds(0, 0, 22, null),
+                    position: new OpenLayers.Pixel(0, -629)
+                },
+                { //bottom-right
+                    size: new OpenLayers.Size(22, 21),
+                    anchor: new OpenLayers.Bounds(null, 0, 0, null),
+                    position: new OpenLayers.Pixel(-638, -629)
+                },
+                { // stem
+                    size: new OpenLayers.Size(81, 54),
+                    anchor: new OpenLayers.Bounds(null, null, 0, 0),
+                    position: new OpenLayers.Pixel(-101, -674)
+                }
+            ]
+        },
+        "br": {
+            'offset': new OpenLayers.Pixel(-44, 0),
+            'padding': new OpenLayers.Bounds(8, 9, 8, 40),
+            'blocks': [
+                { // top-left
+                    size: new OpenLayers.Size('auto', 'auto'),
+                    anchor: new OpenLayers.Bounds(0, 21, 22, 32),
+                    position: new OpenLayers.Pixel(0, 0)
+                },
+                { //top-right
+                    size: new OpenLayers.Size(22, 'auto'),
+                    anchor: new OpenLayers.Bounds(null, 21, 0, 32),
+                    position: new OpenLayers.Pixel(-638, 0)
+                },
+                { //bottom-left
+                    size: new OpenLayers.Size('auto', 21),
+                    anchor: new OpenLayers.Bounds(0, 0, 22, null),
+                    position: new OpenLayers.Pixel(0, -629)
+                },
+                { //bottom-right
+                    size: new OpenLayers.Size(22, 21),
+                    anchor: new OpenLayers.Bounds(null, 0, 0, null),
+                    position: new OpenLayers.Pixel(-638, -629)
+                },
+                { // stem
+                    size: new OpenLayers.Size(81, 54),
+                    anchor: new OpenLayers.Bounds(0, null, null, 0),
+                    position: new OpenLayers.Pixel(-311, -674)
+                }
+            ]
+        }
+    },
+
+    /**
+     * APIProperty: minSize
+     * {<OpenLayers.Size>}
+     */
+    minSize: new OpenLayers.Size(105, 10),
+
+    /**
+     * APIProperty: maxSize
+     * {<OpenLayers.Size>}
+     */
+    maxSize: new OpenLayers.Size(600, 660),
+
+    /** 
+     * Constructor: OpenLayers.Popup.FramedCloud
+     * 
+     * Parameters:
+     * id - {String}
+     * lonlat - {<OpenLayers.LonLat>}
+     * contentSize - {<OpenLayers.Size>}
+     * contentHTML - {String}
+     * anchor - {Object} Object to which we'll anchor the popup. Must expose 
+     *     a 'size' (<OpenLayers.Size>) and 'offset' (<OpenLayers.Pixel>) 
+     *     (Note that this is generally an <OpenLayers.Icon>).
+     * closeBox - {Boolean}
+     * closeBoxCallback - {Function} Function to be called on closeBox click.
+     */
+    initialize:function(id, lonlat, contentSize, contentHTML, anchor, closeBox, 
+                        closeBoxCallback) {
+
+        this.imageSrc = OpenLayers.Util.getImagesLocation() + 'cloud-popup-relative.png';
+        OpenLayers.Popup.Framed.prototype.initialize.apply(this, arguments);
+        this.contentDiv.className = this.contentDisplayClass;
+    },
+
+    /** 
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        OpenLayers.Popup.Framed.prototype.destroy.apply(this, arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Popup.FramedCloud"
+});
+/* ======================================================================
+    OpenLayers/Control/DragFeature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Drag.js
+ * @requires OpenLayers/Handler/Feature.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DragFeature
+ * Move a feature with a drag.  Create a new control with the
+ *     <OpenLayers.Control.DragFeature> constructor.
+ *
+ * Inherits From:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.DragFeature = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * APIProperty: geometryTypes
+     * {Array(String)} To restrict dragging to a limited set of geometry types,
+     *     send a list of strings corresponding to the geometry class names.
+     */
+    geometryTypes: null,
+    
+    /**
+     * APIProperty: onStart
+     * {Function} Define this function if you want to know when a drag starts.
+     *     The function should expect to receive two arguments: the feature
+     *     that is about to be dragged and the pixel location of the mouse.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature that is about to be
+     *     dragged.
+     * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+     */
+    onStart: function(feature, pixel) {},
+
+    /**
+     * APIProperty: onDrag
+     * {Function} Define this function if you want to know about each move of a
+     *     feature. The function should expect to receive two arguments: the
+     *     feature that is being dragged and the pixel location of the mouse.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+     * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+     */
+    onDrag: function(feature, pixel) {},
+
+    /**
+     * APIProperty: onComplete
+     * {Function} Define this function if you want to know when a feature is
+     *     done dragging. The function should expect to receive two arguments:
+     *     the feature that is being dragged and the pixel location of the
+     *     mouse.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature that was dragged.
+     * pixel - {<OpenLayers.Pixel>} The pixel location of the mouse.
+     */
+    onComplete: function(feature, pixel) {},
+
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>}
+     */
+    layer: null,
+    
+    /**
+     * Property: feature
+     * {<OpenLayers.Feature.Vector>}
+     */
+    feature: null,
+
+    /**
+     * Property: dragCallbacks
+     * {Object} The functions that are sent to the drag handler for callback.
+     */
+    dragCallbacks: {},
+
+    /**
+     * Property: featureCallbacks
+     * {Object} The functions that are sent to the feature handler for callback.
+     */
+    featureCallbacks: {},
+    
+    /**
+     * Property: lastPixel
+     * {<OpenLayers.Pixel>}
+     */
+    lastPixel: null,
+
+    /**
+     * Constructor: OpenLayers.Control.DragFeature
+     * Create a new control to drag features.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.Vector>} The layer containing features to be
+     *     dragged.
+     * options - {Object} Optional object whose properties will be set on the
+     *     control.
+     */
+    initialize: function(layer, options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.layer = layer;
+        this.handlers = {
+            drag: new OpenLayers.Handler.Drag(
+                this, OpenLayers.Util.extend({
+                    down: this.downFeature,
+                    move: this.moveFeature,
+                    up: this.upFeature,
+                    out: this.cancel,
+                    done: this.doneDragging
+                }, this.dragCallbacks)
+            ),
+            feature: new OpenLayers.Handler.Feature(
+                this, this.layer, OpenLayers.Util.extend({
+                    over: this.overFeature,
+                    out: this.outFeature
+                }, this.featureCallbacks),
+                {geometryTypes: this.geometryTypes}
+            )
+        };
+    },
+    
+    /**
+     * APIMethod: destroy
+     * Take care of things that are not handled in superclass
+     */
+    destroy: function() {
+        this.layer = null;
+        OpenLayers.Control.prototype.destroy.apply(this, []);
+    },
+
+    /**
+     * APIMethod: activate
+     * Activate the control and the feature handler.
+     * 
+     * Returns:
+     * {Boolean} Successfully activated the control and feature handler.
+     */
+    activate: function() {
+        return (this.handlers.feature.activate() &&
+                OpenLayers.Control.prototype.activate.apply(this, arguments));
+    },
+
+    /**
+     * APIMethod: deactivate
+     * Deactivate the control and all handlers.
+     * 
+     * Returns:
+     * {Boolean} Successfully deactivated the control.
+     */
+    deactivate: function() {
+        // the return from the handlers is unimportant in this case
+        this.handlers.drag.deactivate();
+        this.handlers.feature.deactivate();
+        this.feature = null;
+        this.dragging = false;
+        this.lastPixel = null;
+        return OpenLayers.Control.prototype.deactivate.apply(this, arguments);
+    },
+
+    /**
+     * Method: overFeature
+     * Called when the feature handler detects a mouse-over on a feature.
+     *     This activates the drag handler.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The selected feature.
+     */
+    overFeature: function(feature) {
+        if(!this.handlers.drag.dragging) {
+            this.feature = feature;
+            this.handlers.drag.activate();
+            this.over = true;
+            // TBD replace with CSS classes
+            this.map.div.style.cursor = "move";
+        } else {
+            if(this.feature.id == feature.id) {
+                this.over = true;
+            } else {
+                this.over = false;
+            }
+        }
+    },
+
+    /**
+     * Method: downFeature
+     * Called when the drag handler detects a mouse-down.
+     *
+     * Parameters:
+     * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+     */
+    downFeature: function(pixel) {
+        this.lastPixel = pixel;
+        this.onStart(this.feature, pixel);
+    },
+
+    /**
+     * Method: moveFeature
+     * Called when the drag handler detects a mouse-move.  Also calls the
+     *     optional onDrag method.
+     * 
+     * Parameters:
+     * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+     */
+    moveFeature: function(pixel) {
+        var res = this.map.getResolution();
+        this.feature.geometry.move(res * (pixel.x - this.lastPixel.x),
+                                   res * (this.lastPixel.y - pixel.y));
+        this.layer.drawFeature(this.feature);
+        this.lastPixel = pixel;
+        this.onDrag(this.feature, pixel);
+    },
+
+    /**
+     * Method: upFeature
+     * Called when the drag handler detects a mouse-up.  Also calls the
+     *     optional onComplete method.
+     * 
+     * Parameters:
+     * pixel - {<OpenLayers.Pixel>} Location of the mouse event.
+     */
+    upFeature: function(pixel) {
+        if(!this.over) {
+            this.handlers.drag.deactivate();
+            this.feature = null;
+            // TBD replace with CSS classes
+            this.map.div.style.cursor = "default";
+        } else {
+            // the drag handler itself resetted the cursor, so
+            // set it back to "move" here
+            this.map.div.style.cursor = "move";
+        }
+    },
+
+    /**
+     * Method: doneDragging
+     * Called when the drag handler is done dragging.
+     *
+     * Parameters:
+     * pixel - {<OpenLayers.Pixel>} The last event pixel location.  If this event
+     *     came from a mouseout, this may not be in the map viewport.
+     */
+    doneDragging: function(pixel) {
+        this.onComplete(this.feature, pixel);
+    },
+
+    /**
+     * Method: outFeature
+     * Called when the feature handler detects a mouse-out on a feature.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature that the mouse left.
+     */
+    outFeature: function(feature) {
+        if(!this.handlers.drag.dragging) {
+            this.over = false;
+            this.handlers.drag.deactivate();
+            // TBD replace with CSS classes
+            this.map.div.style.cursor = "default";
+            this.feature = null;
+        } else {
+            if(this.feature.id == feature.id) {
+                this.over = false;
+            }
+        }
+    },
+        
+    /**
+     * Method: cancel
+     * Called when the drag handler detects a mouse-out (from the map viewport).
+     */
+    cancel: function() {
+        this.handlers.drag.deactivate();
+        this.over = false;
+    },
+
+    /**
+     * Method: setMap
+     * Set the map property for the control and all handlers.
+     *
+     * Parameters: 
+     * map - {<OpenLayers.Map>} The control's map.
+     */
+    setMap: function(map) {
+        this.handlers.drag.setMap(map);
+        this.handlers.feature.setMap(map);
+        OpenLayers.Control.prototype.setMap.apply(this, arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Control.DragFeature"
+});
+/* ======================================================================
     OpenLayers/Control/DragPan.js
    ====================================================================== */
 
@@ -13401,13 +27030,26 @@
     panned: false,
     
     /**
+     * Property: interval
+     * {Integer} The number of milliseconds that should ellapse before
+     *     panning the map again. Set this to increase dragging performance.
+     *     Defaults to 25 milliseconds.
+     */
+    interval: 25, 
+    
+    /**
      * Method: draw
      * Creates a Drag handler, using <panMap> and
      * <panMapDone> as callbacks.
      */    
     draw: function() {
-        this.handler = new OpenLayers.Handler.Drag(this,
-                            {"move": this.panMap, "done": this.panMapDone});
+        this.handler = new OpenLayers.Handler.Drag(this, {
+                "move": this.panMap,
+                "done": this.panMapDone
+            }, {
+                interval: this.interval
+            }
+        );
     },
 
     /**
@@ -13443,6 +27085,777 @@
     CLASS_NAME: "OpenLayers.Control.DragPan"
 });
 /* ======================================================================
+    OpenLayers/Control/KeyboardDefaults.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Handler/Keyboard.js
+ */
+
+/**
+ * Class: OpenLayers.Control.KeyboardDefaults
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.KeyboardDefaults = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * APIProperty: slideFactor
+     * Pixels to slide by.
+     */
+    slideFactor: 75,
+
+    /**
+     * Constructor: OpenLayers.Control.KeyboardDefaults
+     */
+    initialize: function() {
+        OpenLayers.Control.prototype.initialize.apply(this, arguments);
+    },
+    
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        if (this.handler) {
+            this.handler.destroy();
+        }        
+        this.handler = null;
+        
+        OpenLayers.Control.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * Method: draw
+     * Create handler.
+     */
+    draw: function() {
+        this.handler = new OpenLayers.Handler.Keyboard( this, { 
+                                "keydown": this.defaultKeyPress });
+        this.activate();
+    },
+    
+    /**
+     * Method: defaultKeyPress
+     * When handling the key event, we only use evt.keyCode. This holds 
+     * some drawbacks, though we get around them below. When interpretting
+     * the keycodes below (including the comments associated with them),
+     * consult the URL below. For instance, the Safari browser returns
+     * "IE keycodes", and so is supported by any keycode labeled "IE".
+     * 
+     * Very informative URL:
+     *    http://unixpapa.com/js/key.html
+     *
+     * Parameters:
+     * code - {Integer} 
+     */
+    defaultKeyPress: function (evt) {
+        switch(evt.keyCode) {
+            case OpenLayers.Event.KEY_LEFT:
+                this.map.pan(-this.slideFactor, 0);
+                break;
+            case OpenLayers.Event.KEY_RIGHT: 
+                this.map.pan(this.slideFactor, 0);
+                break;
+            case OpenLayers.Event.KEY_UP:
+                this.map.pan(0, -this.slideFactor);
+                break;
+            case OpenLayers.Event.KEY_DOWN:
+                this.map.pan(0, this.slideFactor);
+                break;
+            
+            case 33: // Page Up. Same in all browsers.
+                var size = this.map.getSize();
+                this.map.pan(0, -0.75*size.h);
+                break;
+            case 34: // Page Down. Same in all browsers.
+                var size = this.map.getSize();
+                this.map.pan(0, 0.75*size.h);
+                break; 
+            case 35: // End. Same in all browsers.
+                var size = this.map.getSize();
+                this.map.pan(0.75*size.w, 0);
+                break; 
+            case 36: // Home. Same in all browsers.
+                var size = this.map.getSize();
+                this.map.pan(-0.75*size.w, 0);
+                break; 
+
+            case 43:  // +/= (ASCII), keypad + (ASCII, Opera)
+            case 61:  // +/= (Mozilla, Opera, some ASCII)
+            case 187: // +/= (IE)
+            case 107: // keypad + (IE, Mozilla)
+                this.map.zoomIn();
+                break; 
+            case 45:  // -/_ (ASCII, Opera), keypad - (ASCII, Opera)
+            case 109: // -/_ (Mozilla), keypad - (Mozilla, IE)
+            case 189: // -/_ (IE)
+            case 95:  // -/_ (some ASCII)
+                this.map.zoomOut();
+                break; 
+        } 
+    },
+
+    CLASS_NAME: "OpenLayers.Control.KeyboardDefaults"
+});
+/* ======================================================================
+    OpenLayers/Feature/Vector.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+// TRASH THIS
+OpenLayers.State = {
+    /** states */
+    UNKNOWN: 'Unknown',
+    INSERT: 'Insert',
+    UPDATE: 'Update',
+    DELETE: 'Delete'
+};
+
+/**
+ * @requires OpenLayers/Feature.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Feature.Vector
+ * Vector features use the OpenLayers.Geometry classes as geometry description.
+ * They have an 'attributes' property, which is the data object, and a 'style'
+ * property, the default values of which are defined in the 
+ * <OpenLayers.Feature.Vector.style> objects.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Feature>
+ */
+OpenLayers.Feature.Vector = OpenLayers.Class(OpenLayers.Feature, {
+
+    /** 
+     * Property: fid 
+     * {String} 
+     */
+    fid: null,
+    
+    /** 
+     * APIProperty: geometry 
+     * {<OpenLayers.Geometry>} 
+     */
+    geometry: null,
+
+    /** 
+     * APIProperty: attributes 
+     * {Object} This object holds arbitrary properties that describe the
+     *     feature.
+     */
+    attributes: null,
+
+    /** 
+     * Property: state 
+     * {String} 
+     */
+    state: null,
+    
+    /** 
+     * APIProperty: style 
+     * {Object} 
+     */
+    style: null,
+    
+    /**
+     * Property: renderIntent
+     * {String} rendering intent currently being used
+     */
+    renderIntent: "default",
+
+    /** 
+     * Constructor: OpenLayers.Feature.Vector
+     * Create a vector feature. 
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} The geometry that this feature
+     *     represents.
+     * attributes - {Object} An optional object that will be mapped to the
+     *     <attributes> property. 
+     * style - {Object} An optional style object.
+     */
+    initialize: function(geometry, attributes, style) {
+        OpenLayers.Feature.prototype.initialize.apply(this,
+                                                      [null, null, attributes]);
+        this.lonlat = null;
+        this.geometry = geometry ? geometry : null;
+        this.state = null;
+        this.attributes = {};
+        if (attributes) {
+            this.attributes = OpenLayers.Util.extend(this.attributes,
+                                                     attributes);
+        }
+        this.style = style ? style : null; 
+    },
+    
+    /** 
+     * Method: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+        if (this.layer) {
+            this.layer.removeFeatures(this);
+            this.layer = null;
+        }
+            
+        this.geometry = null;
+        OpenLayers.Feature.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * Method: clone
+     * Create a clone of this vector feature.  Does not set any non-standard
+     *     properties.
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} An exact clone of this vector feature.
+     */
+    clone: function () {
+        return new OpenLayers.Feature.Vector(
+            this.geometry ? this.geometry.clone() : null,
+            this.attributes,
+            this.style);
+    },
+
+    /**
+     * Method: onScreen
+     * Determine whether the feature is within the map viewport.  This method
+     *     tests for an intersection between the geometry and the viewport
+     *     bounds.  If a more effecient but less precise geometry bounds
+     *     intersection is desired, call the method with the boundsOnly
+     *     parameter true.
+     *
+     * Parameters:
+     * boundsOnly - {Boolean} Only test whether a feature's bounds intersects
+     *     the viewport bounds.  Default is false.  If false, the feature's
+     *     geometry must intersect the viewport for onScreen to return true.
+     * 
+     * Returns:
+     * {Boolean} The feature is currently visible on screen (optionally
+     *     based on its bounds if boundsOnly is true).
+     */
+    onScreen:function(boundsOnly) {
+        var onScreen = false;
+        if(this.layer && this.layer.map) {
+            var screenBounds = this.layer.map.getExtent();
+            if(boundsOnly) {
+                var featureBounds = this.geometry.getBounds();
+                onScreen = screenBounds.intersectsBounds(featureBounds);
+            } else {
+                var screenPoly = screenBounds.toGeometry();
+                onScreen = screenPoly.intersects(this.geometry);
+            }
+        }    
+        return onScreen;
+    },
+    
+    /**
+     * Method: createMarker
+     * HACK - we need to decide if all vector features should be able to
+     *     create markers
+     * 
+     * Returns:
+     * {<OpenLayers.Marker>} For now just returns null
+     */
+    createMarker: function() {
+        return null;
+    },
+
+    /**
+     * Method: destroyMarker
+     * HACK - we need to decide if all vector features should be able to
+     *     delete markers
+     * 
+     * If user overrides the createMarker() function, s/he should be able
+     *   to also specify an alternative function for destroying it
+     */
+    destroyMarker: function() {
+        // pass
+    },
+
+    /**
+     * Method: createPopup
+     * HACK - we need to decide if all vector features should be able to
+     *     create popups
+     * 
+     * Returns:
+     * {<OpenLayers.Popup>} For now just returns null
+     */
+    createPopup: function() {
+        return null;
+    },
+
+    /**
+     * Method: atPoint
+     * Determins whether the feature intersects with the specified location.
+     * 
+     * Parameters: 
+     * lonlat - {<OpenLayers.LonLat>} 
+     * toleranceLon - {float} Optional tolerance in Geometric Coords
+     * toleranceLat - {float} Optional tolerance in Geographic Coords
+     * 
+     * Returns:
+     * {Boolean} Whether or not the feature is at the specified location
+     */
+    atPoint: function(lonlat, toleranceLon, toleranceLat) {
+        var atPoint = false;
+        if(this.geometry) {
+            atPoint = this.geometry.atPoint(lonlat, toleranceLon, 
+                                                    toleranceLat);
+        }
+        return atPoint;
+    },
+
+    /**
+     * Method: destroyPopup
+     * HACK - we need to decide if all vector features should be able to
+     * delete popups
+     */
+    destroyPopup: function() {
+        // pass
+    },
+
+    /**
+     * Method: move
+     * Moves the feature and redraws it at its new location
+     *
+     * Parameters:
+     * state - {OpenLayers.LonLat or OpenLayers.Pixel} the
+     *         location to which to move the feature.
+     */
+    move: function(location) {
+
+        if(!this.layer || !this.geometry.move){
+            //do nothing if no layer or immoveable geometry
+            return;
+        }
+
+        var pixel;
+        if (location.CLASS_NAME == "OpenLayers.LonLat") {
+            pixel = this.layer.getViewPortPxFromLonLat(location);
+        } else {
+            pixel = location;
+        }
+        
+        var lastPixel = this.layer.getViewPortPxFromLonLat(this.geometry.getBounds().getCenterLonLat());
+        var res = this.layer.map.getResolution();
+        this.geometry.move(res * (pixel.x - lastPixel.x),
+                           res * (lastPixel.y - pixel.y));
+        this.layer.drawFeature(this);
+        return lastPixel;
+    },
+    
+    /**
+     * Method: toState
+     * Sets the new state
+     *
+     * Parameters:
+     * state - {String} 
+     */
+    toState: function(state) {
+        if (state == OpenLayers.State.UPDATE) {
+            switch (this.state) {
+                case OpenLayers.State.UNKNOWN:
+                case OpenLayers.State.DELETE:
+                    this.state = state;
+                    break;
+                case OpenLayers.State.UPDATE:
+                case OpenLayers.State.INSERT:
+                    break;
+            }
+        } else if (state == OpenLayers.State.INSERT) {
+            switch (this.state) {
+                case OpenLayers.State.UNKNOWN:
+                    break;
+                default:
+                    this.state = state;
+                    break;
+            }
+        } else if (state == OpenLayers.State.DELETE) {
+            switch (this.state) {
+                case OpenLayers.State.INSERT:
+                    // the feature should be destroyed
+                    break;
+                case OpenLayers.State.DELETE:
+                    break;
+                case OpenLayers.State.UNKNOWN:
+                case OpenLayers.State.UPDATE:
+                    this.state = state;
+                    break;
+            }
+        } else if (state == OpenLayers.State.UNKNOWN) {
+            this.state = state;
+        }
+    },
+    
+    CLASS_NAME: "OpenLayers.Feature.Vector"
+});
+
+
+/**
+ * Constant: OpenLayers.Feature.Vector.style
+ * OpenLayers features can have a number of style attributes. The 'default' 
+ *     style will typically be used if no other style is specified.
+ *
+ * Default style properties:
+ *
+ *  - fillColor: "#ee9900",
+ *  - fillOpacity: 0.4, 
+ *  - hoverFillColor: "white",
+ *  - hoverFillOpacity: 0.8,
+ *  - strokeColor: "#ee9900",
+ *  - strokeOpacity: 1,
+ *  - strokeWidth: 1,
+ *  - strokeLinecap: "round",  [butt | round | square]
+ *  - strokeDashstyle: "solid", [dot | dash | dashdot | longdash | longdashdot | solid]
+ *  - hoverStrokeColor: "red",
+ *  - hoverStrokeOpacity: 1,
+ *  - hoverStrokeWidth: 0.2,
+ *  - pointRadius: 6,
+ *  - hoverPointRadius: 1,
+ *  - hoverPointUnit: "%",
+ *  - pointerEvents: "visiblePainted"
+ *  - cursor: ""
+ *
+ * Other style properties that have no default values:
+ *
+ *  - externalGraphic,
+ *  - graphicWidth,
+ *  - graphicHeight,
+ *  - graphicOpacity,
+ *  - graphicXOffset,
+ *  - graphicYOffset,
+ *  - graphicName,
+ *  - display
+ */ 
+OpenLayers.Feature.Vector.style = {
+    'default': {
+        fillColor: "#ee9900",
+        fillOpacity: 0.4, 
+        hoverFillColor: "white",
+        hoverFillOpacity: 0.8,
+        strokeColor: "#ee9900",
+        strokeOpacity: 1,
+        strokeWidth: 1,
+        strokeLinecap: "round",
+        strokeDashstyle: "solid",
+        hoverStrokeColor: "red",
+        hoverStrokeOpacity: 1,
+        hoverStrokeWidth: 0.2,
+        pointRadius: 6,
+        hoverPointRadius: 1,
+        hoverPointUnit: "%",
+        pointerEvents: "visiblePainted",
+        cursor: "inherit"
+    },
+    'select': {
+        fillColor: "blue",
+        fillOpacity: 0.4, 
+        hoverFillColor: "white",
+        hoverFillOpacity: 0.8,
+        strokeColor: "blue",
+        strokeOpacity: 1,
+        strokeWidth: 2,
+        strokeLinecap: "round",
+        strokeDashstyle: "solid",
+        hoverStrokeColor: "red",
+        hoverStrokeOpacity: 1,
+        hoverStrokeWidth: 0.2,
+        pointRadius: 6,
+        hoverPointRadius: 1,
+        hoverPointUnit: "%",
+        pointerEvents: "visiblePainted",
+        cursor: "pointer"
+    },
+    'temporary': {
+        fillColor: "yellow",
+        fillOpacity: 0.2, 
+        hoverFillColor: "white",
+        hoverFillOpacity: 0.8,
+        strokeColor: "yellow",
+        strokeOpacity: 1,
+        strokeLinecap: "round",
+        strokeWidth: 4,
+        strokeDashstyle: "solid",
+        hoverStrokeColor: "red",
+        hoverStrokeOpacity: 1,
+        hoverStrokeWidth: 0.2,
+        pointRadius: 6,
+        hoverPointRadius: 1,
+        hoverPointUnit: "%",
+        pointerEvents: "visiblePainted",
+        cursor: "inherit"
+    }
+};    
+/* ======================================================================
+    OpenLayers/Feature/WFS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Feature.js
+ */
+
+/**
+ * Class: OpenLayers.Feature.WFS
+ * WFS handling class, for use as a featureClass on the WFS layer for handling
+ * 'point' WFS types. Good for subclassing when creating a custom WFS like
+ * XML application.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Feature>
+ */
+OpenLayers.Feature.WFS = OpenLayers.Class(OpenLayers.Feature, {
+      
+    /** 
+     * Constructor: OpenLayers.Feature.WFS
+     * Create a WFS feature.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer>} 
+     * xmlNode - {XMLNode} 
+     */
+    initialize: function(layer, xmlNode) {
+        var newArguments = arguments;
+        var data = this.processXMLNode(xmlNode);
+        newArguments = new Array(layer, data.lonlat, data);
+        OpenLayers.Feature.prototype.initialize.apply(this, newArguments);
+        this.createMarker();
+        this.layer.addMarker(this.marker);
+    },
+    
+    /** 
+     * Method: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+        if (this.marker != null) {
+            this.layer.removeMarker(this.marker);  
+        }
+        OpenLayers.Feature.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * Method: processXMLNode
+     * When passed an xmlNode, parses it for a GML point, and passes
+     * back an object describing that point.
+     *
+     * For subclasses of Feature.WFS, this is the feature to change.
+     *
+     * Parameters:
+     * xmlNode - {XMLNode} 
+     * 
+     * Returns:
+     * {Object} Data Object with 'id', 'lonlat', and private properties set
+     */
+    processXMLNode: function(xmlNode) {
+        //this should be overridden by subclasses
+        // must return an Object with 'id' and 'lonlat' values set
+        var point = OpenLayers.Ajax.getElementsByTagNameNS(xmlNode, "http://www.opengis.net/gml", "gml", "Point");
+        var text  = OpenLayers.Util.getXmlNodeValue(OpenLayers.Ajax.getElementsByTagNameNS(point[0], "http://www.opengis.net/gml","gml", "coordinates")[0]);
+        var floats = text.split(",");
+        return {lonlat: new OpenLayers.LonLat(parseFloat(floats[0]),
+                                              parseFloat(floats[1])),
+                id: null};
+
+    },
+
+    CLASS_NAME: "OpenLayers.Feature.WFS"
+});
+  
+  
+  
+  
+
+/* ======================================================================
+    OpenLayers/Format/WMC/v1_0_0.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMC/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1_0_0
+ * Read and write WMC version 1.0.0.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.WMC.v1>
+ */
+OpenLayers.Format.WMC.v1_0_0 = OpenLayers.Class(
+    OpenLayers.Format.WMC.v1, {
+    
+    /**
+     * Constant: VERSION
+     * {String} 1.0.0
+     */
+    VERSION: "1.0.0",
+    
+    /**
+     * Property: schemaLocation
+     * {String} http://www.opengis.net/context
+     *     http://schemas.opengis.net/context/1.0.0/context.xsd
+     */
+    schemaLocation: "http://www.opengis.net/context http://schemas.opengis.net/context/1.0.0/context.xsd",
+
+    /**
+     * Constructor: OpenLayers.Format.WMC.v1_0_0
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.WMC> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.WMC.v1.prototype.initialize.apply(
+            this, [options]
+        );
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WMC.v1_0_0" 
+
+});
+/* ======================================================================
+    OpenLayers/Format/WMC/v1_1_0.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/WMC/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WMC.v1_1_0
+ * Read and write WMC version 1.1.0.
+ *
+ * Differences between 1.1.0 and 1.0.0:
+ *     - 1.1.0 Layers have optional sld:MinScaleDenominator and
+ *       sld:MaxScaleDenominator
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.WMC.v1>
+ */
+OpenLayers.Format.WMC.v1_1_0 = OpenLayers.Class(
+    OpenLayers.Format.WMC.v1, {
+    
+    /**
+     * Constant: VERSION
+     * {String} 1.1.0
+     */
+    VERSION: "1.1.0",
+
+    /**
+     * Property: schemaLocation
+     * {String} http://www.opengis.net/context
+     *     http://schemas.opengis.net/context/1.1.0/context.xsd
+     */
+    schemaLocation: "http://www.opengis.net/context http://schemas.opengis.net/context/1.1.0/context.xsd",
+
+    /**
+     * Constructor: OpenLayers.Format.WMC.v1_1_0
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.WMC> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.WMC.v1.prototype.initialize.apply(
+            this, [options]
+        );
+    },
+
+    /**
+     * Method: read_sld_MinScaleDenominator
+     * Read a sld:MinScaleDenominator node.
+     *
+     * Parameters:
+     * layerInfo - {Object} An object representing a layer.
+     * node - {Element} An element node.
+     */
+    read_sld_MinScaleDenominator: function(layerInfo, node) {
+        layerInfo.options.maxScale = this.getChildValue(node);
+    },
+
+    /**
+     * Method: read_sld_MaxScaleDenominator
+     * Read a sld:MaxScaleDenominator node.
+     *
+     * Parameters:
+     * layerInfo - {Object} An object representing a layer.
+     * node - {Element} An element node.
+     */
+    read_sld_MaxScaleDenominator: function(layerInfo, node) {
+        layerInfo.options.minScale = this.getChildValue(node);
+    },
+
+    /**
+     * Method: write_wmc_Layer
+     * Create a Layer node given a layer object.  This method adds elements
+     *     specific to version 1.1.0.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.WMS>} Layer object.
+     *
+     * Returns:
+     * {Element} A WMC Layer element node.
+     */
+    write_wmc_Layer: function(layer) {
+        var node = OpenLayers.Format.WMC.v1.prototype.write_wmc_Layer.apply(
+            this, [layer]
+        );
+        
+        // min/max scale denominator elements go before the 4th element in v1
+        if(layer.options.resolutions || layer.options.scales ||
+           layer.options.minResolution || layer.options.maxScale) {
+            var minSD = this.createElementNS(
+                this.namespaces.sld, "sld:MinScaleDenominator"
+            );
+            minSD.appendChild(this.createTextNode(layer.maxScale.toPrecision(10)));
+            node.insertBefore(minSD, node.childNodes[3]);
+        }
+        
+        if(layer.options.resolutions || layer.options.scales ||
+           layer.options.maxResolution || layer.options.minScale) {
+            var maxSD = this.createElementNS(
+                this.namespaces.sld, "sld:MaxScaleDenominator"
+            );
+            maxSD.appendChild(this.createTextNode(layer.minScale.toPrecision(10)));
+            node.insertBefore(maxSD, node.childNodes[4]);
+        }
+        
+        return node;
+        
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WMC.v1_1_0" 
+
+});
+/* ======================================================================
     OpenLayers/Handler/Box.js
    ====================================================================== */
 
@@ -13477,6 +27890,13 @@
      *     olHandlerBoxZoomBox
      */
     boxDivClassName: 'olHandlerBoxZoomBox',
+    
+    /**
+     * Property: boxCharacteristics
+     * {Object} Caches some box characteristics from css. This is used
+     *     by the getBoxCharacteristics method.
+     */
+    boxCharacteristics: null,
 
     /**
      * Constructor: OpenLayers.Handler.Box
@@ -13532,16 +27952,28 @@
     * Method: moveBox
     */
     moveBox: function (xy) {
-        var deltaX = Math.abs(this.dragHandler.start.x - xy.x);
-        var deltaY = Math.abs(this.dragHandler.start.y - xy.y);
+        var startX = this.dragHandler.start.x;
+        var startY = this.dragHandler.start.y;
+        var deltaX = Math.abs(startX - xy.x);
+        var deltaY = Math.abs(startY - xy.y);
         this.zoomBox.style.width = Math.max(1, deltaX) + "px";
         this.zoomBox.style.height = Math.max(1, deltaY) + "px";
-        if (xy.x < this.dragHandler.start.x) {
-            this.zoomBox.style.left = xy.x+"px";
+        this.zoomBox.style.left = xy.x < startX ? xy.x+"px" : startX+"px";
+        this.zoomBox.style.top = xy.y < startY ? xy.y+"px" : startY+"px";
+
+        // depending on the box model, modify width and height to take borders
+        // of the box into account
+        var box = this.getBoxCharacteristics(deltaX, deltaY);
+        if (box.newBoxModel) {
+            if (xy.x > startX) {
+                this.zoomBox.style.width =
+                    Math.max(1, deltaX - box.xOffset) + "px";
+            }
+            if (xy.y > startY) {
+                this.zoomBox.style.height =
+                    Math.max(1, deltaY - box.yOffset) + "px";
+            }
         }
-        if (xy.y < this.dragHandler.start.y) {
-            this.zoomBox.style.top = xy.y+"px";
-        }
     },
 
     /**
@@ -13575,6 +28007,7 @@
     removeBox: function() {
         this.map.viewPortDiv.removeChild(this.zoomBox);
         this.zoomBox = null;
+        this.boxCharacteristics = null;
     },
 
     /**
@@ -13600,10 +28033,1111 @@
             return false;
         }
     },
+    
+    getBoxCharacteristics: function(dx, dy) {
+        if (!this.boxCharacteristics) {
+            var xOffset = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+                "border-left-width")) + parseInt(OpenLayers.Element.getStyle(
+                this.zoomBox, "border-right-width")) + 1;
+            var yOffset = parseInt(OpenLayers.Element.getStyle(this.zoomBox,
+                "border-top-width")) + parseInt(OpenLayers.Element.getStyle(
+                this.zoomBox, "border-bottom-width")) + 1;
+            // all browsers use the new box model, except IE in quirks mode
+            var newBoxModel = OpenLayers.Util.getBrowserName() == "msie" ?
+                document.compatMode != "BackCompat" : true;
+            this.boxCharacteristics = {
+                xOffset: xOffset,
+                yOffset: yOffset,
+                newBoxModel: newBoxModel
+            }
+        }
+        return this.boxCharacteristics;
+    },
 
     CLASS_NAME: "OpenLayers.Handler.Box"
 });
 /* ======================================================================
+    OpenLayers/Handler/RegularPolygon.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Drag.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.RegularPolygon
+ * Handler to draw a regular polygon on the map.  Polygon is displayed on mouse
+ *     down, moves or is modified on mouse move, and is finished on mouse up.
+ *     The handler triggers callbacks for 'done' and 'cancel'.  Create a new
+ *     instance with the <OpenLayers.Handler.RegularPolygon> constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.RegularPolygon = OpenLayers.Class(OpenLayers.Handler.Drag, {
+    
+    /**
+     * APIProperty: sides
+     * {Integer} Number of sides for the regular polygon.  Needs to be greater
+     *     than 2.  Defaults to 4.
+     */
+    sides: 4,
+
+    /**
+     * APIProperty: radius
+     * {Float} Optional radius in map units of the regular polygon.  If this is
+     *     set to some non-zero value, a polygon with a fixed radius will be
+     *     drawn and dragged with mose movements.  If this property is not
+     *     set, dragging changes the radius of the polygon.  Set to null by
+     *     default.
+     */
+    radius: null,
+    
+    /**
+     * APIProperty: snapAngle
+     * {Float} If set to a non-zero value, the handler will snap the polygon
+     *     rotation to multiples of the snapAngle.  Value is an angle measured
+     *     in degrees counterclockwise from the positive x-axis.  
+     */
+    snapAngle: null,
+    
+    /**
+     * APIProperty: snapToggle
+     * {String} If set, snapToggle is checked on mouse events and will set
+     *     the snap mode to the opposite of what it currently is.  To disallow
+     *     toggling between snap and non-snap mode, set freehandToggle to
+     *     null.  Acceptable toggle values are 'shiftKey', 'ctrlKey', and
+     *     'altKey'. Snap mode is only possible if this.snapAngle is set to a
+     *     non-zero value.
+     */
+    snapToggle: 'shiftKey',
+    
+    /**
+     * APIProperty: persist
+     * {Boolean} Leave the feature rendered until clear is called.  Default
+     *     is false.  If set to true, the feature remains rendered until
+     *     clear is called, typically by deactivating the handler or starting
+     *     another drawing.
+     */
+    persist: false,
+
+    /**
+     * APIProperty: irregular
+     * {Boolean} Draw an irregular polygon instead of a regular polygon.
+     *     Default is false.  If true, the initial mouse down will represent
+     *     one corner of the polygon bounds and with each mouse movement, the
+     *     polygon will be stretched so the opposite corner of its bounds
+     *     follows the mouse position.  This property takes precedence over
+     *     the radius property.  If set to true, the radius property will
+     *     be ignored.
+     */
+    irregular: false,
+
+    /**
+     * Property: angle
+     * {Float} The angle from the origin (mouse down) to the current mouse
+     *     position, in radians.  This is measured counterclockwise from the
+     *     positive x-axis.
+     */
+    angle: null,
+
+    /**
+     * Property: fixedRadius
+     * {Boolean} The polygon has a fixed radius.  True if a radius is set before
+     *     drawing begins.  False otherwise.
+     */
+    fixedRadius: false,
+
+    /**
+     * Property: feature
+     * {<OpenLayers.Feature.Vector>} The currently drawn polygon feature
+     */
+    feature: null,
+
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>} The temporary drawing layer
+     */
+    layer: null,
+
+    /**
+     * Property: origin
+     * {<OpenLayers.Geometry.Point>} Location of the first mouse down
+     */
+    origin: null,
+
+    /**
+     * Constructor: OpenLayers.Handler.RegularPolygon
+     * Create a new regular polygon handler.
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} The control that owns this handler
+     * callbacks - {Array} An object with a 'done' property whos value is a
+     *     function to be called when the polygon drawing is finished.
+     *     The callback should expect to recieve a single argument,
+     *     the polygon geometry.  If the callbacks object contains a
+     *     'cancel' property, this function will be called when the
+     *     handler is deactivated while drawing.  The cancel should
+     *     expect to receive a geometry.
+     * options - {Object} An object with properties to be set on the handler.
+     *     If the options.sides property is not specified, the number of sides
+     *     will default to 4.
+     */
+    initialize: function(control, callbacks, options) {
+        this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
+
+        OpenLayers.Handler.prototype.initialize.apply(this,
+                                                [control, callbacks, options]);
+        this.options = (options) ? options : new Object();
+    },
+    
+    /**
+     * APIMethod: setOptions
+     * 
+     * Parameters:
+     * newOptions - {Object} 
+     */
+    setOptions: function (newOptions) {
+        OpenLayers.Util.extend(this.options, newOptions);
+        OpenLayers.Util.extend(this, newOptions);
+    },
+    
+    /**
+     * APIMethod: activate
+     * Turn on the handler.
+     *
+     * Return:
+     * {Boolean} The handler was successfully activated
+     */
+    activate: function() {
+        var activated = false;
+        if(OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+            // create temporary vector layer for rendering geometry sketch
+            var options = {
+                displayInLayerSwitcher: false,
+                // indicate that the temp vector layer will never be out of range
+                // without this, resolution properties must be specified at the
+                // map-level for this temporary layer to init its resolutions
+                // correctly
+                calculateInRange: function() { return true; }
+            };
+            this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
+            this.map.addLayer(this.layer);
+            activated = true;
+        }
+        return activated;
+    },
+
+    /**
+     * APIMethod: deactivate
+     * Turn off the handler.
+     *
+     * Return:
+     * {Boolean} The handler was successfully deactivated
+     */
+    deactivate: function() {
+        var deactivated = false;
+        if(OpenLayers.Handler.Drag.prototype.deactivate.apply(this, arguments)) {
+            // call the cancel callback if mid-drawing
+            if(this.dragging) {
+                this.cancel();
+            }
+            // If a layer's map property is set to null, it means that that
+            // layer isn't added to the map. Since we ourself added the layer
+            // to the map in activate(), we can assume that if this.layer.map
+            // is null it means that the layer has been destroyed (as a result
+            // of map.destroy() for example.
+            if (this.layer.map != null) {
+                this.layer.destroy(false);
+                if (this.feature) {
+                    this.feature.destroy();
+                }
+            }
+            this.layer = null;
+            this.feature = null;
+            deactivated = true;
+        }
+        return deactivated;
+    },
+    
+    /**
+     * Method: downFeature
+     * Start drawing a new feature
+     *
+     * Parameters:
+     * evt - {Event} The drag start event
+     */
+    down: function(evt) {
+        this.fixedRadius = !!(this.radius);
+        var maploc = this.map.getLonLatFromPixel(evt.xy);
+        this.origin = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
+        // create the new polygon
+        if(!this.fixedRadius || this.irregular) {
+            // smallest radius should not be less one pixel in map units
+            // VML doesn't behave well with smaller
+            this.radius = this.map.getResolution();
+        }
+        if(this.persist) {
+            this.clear();
+        }
+        this.feature = new OpenLayers.Feature.Vector();
+        this.createGeometry();
+        this.layer.addFeatures([this.feature], {silent: true});
+        this.layer.drawFeature(this.feature, this.style);
+    },
+    
+    /**
+     * Method: move
+     * Respond to drag move events
+     *
+     * Parameters:
+     * evt - {Evt} The move event
+     */
+    move: function(evt) {
+        var maploc = this.map.getLonLatFromPixel(evt.xy);
+        var point = new OpenLayers.Geometry.Point(maploc.lon, maploc.lat);
+        if(this.irregular) {
+            var ry = Math.sqrt(2) * Math.abs(point.y - this.origin.y) / 2;
+            this.radius = Math.max(this.map.getResolution() / 2, ry);
+        } else if(this.fixedRadius) {
+            this.origin = point;
+        } else {
+            this.calculateAngle(point, evt);
+            this.radius = Math.max(this.map.getResolution() / 2,
+                                   point.distanceTo(this.origin));
+        }
+        this.modifyGeometry();
+        if(this.irregular) {
+            var dx = point.x - this.origin.x;
+            var dy = point.y - this.origin.y;
+            var ratio;
+            if(dy == 0) {
+                ratio = dx / (this.radius * Math.sqrt(2));
+            } else {
+                ratio = dx / dy;
+            }
+            this.feature.geometry.resize(1, this.origin, ratio);
+            this.feature.geometry.move(dx / 2, dy / 2);
+        }
+        this.layer.drawFeature(this.feature, this.style);
+    },
+
+    /**
+     * Method: up
+     * Finish drawing the feature
+     *
+     * Parameters:
+     * evt - {Event} The mouse up event
+     */
+    up: function(evt) {
+        this.finalize();
+    },
+
+    /**
+     * Method: out
+     * Finish drawing the feature.
+     *
+     * Parameters:
+     * evt - {Event} The mouse out event
+     */
+    out: function(evt) {
+        this.finalize();
+    },
+
+    /**
+     * Method: createGeometry
+     * Create the new polygon geometry.  This is called at the start of the
+     *     drag and at any point during the drag if the number of sides
+     *     changes.
+     */
+    createGeometry: function() {
+        this.angle = Math.PI * ((1/this.sides) - (1/2));
+        if(this.snapAngle) {
+            this.angle += this.snapAngle * (Math.PI / 180);
+        }
+        this.feature.geometry = OpenLayers.Geometry.Polygon.createRegularPolygon(
+            this.origin, this.radius, this.sides, this.snapAngle
+        );
+    },
+    
+    /**
+     * Method: modifyGeometry
+     * Modify the polygon geometry in place.
+     */
+    modifyGeometry: function() {
+        var angle, dx, dy, point;
+        var ring = this.feature.geometry.components[0];
+        // if the number of sides ever changes, create a new geometry
+        if(ring.components.length != (this.sides + 1)) {
+            this.createGeometry();
+             ring = this.feature.geometry.components[0];
+        }
+        for(var i=0; i<this.sides; ++i) {
+            point = ring.components[i];
+            angle = this.angle + (i * 2 * Math.PI / this.sides);
+            point.x = this.origin.x + (this.radius * Math.cos(angle));
+            point.y = this.origin.y + (this.radius * Math.sin(angle));
+            point.clearBounds();
+        }
+    },
+    
+    /**
+     * Method: calculateAngle
+     * Calculate the angle based on settings.
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>}
+     * evt - {Event}
+     */
+    calculateAngle: function(point, evt) {
+        var alpha = Math.atan2(point.y - this.origin.y,
+                               point.x - this.origin.x);
+        if(this.snapAngle && (this.snapToggle && !evt[this.snapToggle])) {
+            var snapAngleRad = (Math.PI / 180) * this.snapAngle;
+            this.angle = Math.round(alpha / snapAngleRad) * snapAngleRad;
+        } else {
+            this.angle = alpha;
+        }
+    },
+
+    /**
+     * APIMethod: cancel
+     * Finish the geometry and call the "cancel" callback.
+     */
+    cancel: function() {
+        // the polygon geometry gets cloned in the callback method
+        this.callback("cancel", null);
+        this.finalize();
+    },
+
+    /**
+     * Method: finalize
+     * Finish the geometry and call the "done" callback.
+     */
+    finalize: function() {
+        this.origin = null;
+        this.radius = this.options.radius;
+    },
+
+    /**
+     * APIMethod: clear
+     * Clear any rendered features on the temporary layer.  This is called
+     *     when the handler is deactivated, canceled, or done (unless persist
+     *     is true).
+     */
+    clear: function() {
+        this.layer.renderer.clear();
+        this.layer.destroyFeatures();
+    },
+    
+    /**
+     * Method: callback
+     * Trigger the control's named callback with the given arguments
+     *
+     * Parameters:
+     * name - {String} The key for the callback that is one of the properties
+     *     of the handler's callbacks object.
+     * args - {Array} An array of arguments with which to call the callback
+     *     (defined by the control).
+     */
+    callback: function (name, args) {
+        // override the callback method to always send the polygon geometry
+        if (this.callbacks[name]) {
+            this.callbacks[name].apply(this.control,
+                                       [this.feature.geometry.clone()]);
+        }
+        // since sketch features are added to the temporary layer
+        // they must be cleared here if done or cancel
+        if(!this.persist && (name == "done" || name == "cancel")) {
+            this.clear();
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.RegularPolygon"
+});
+/* ======================================================================
+    OpenLayers/Layer/EventPane.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.EventPane
+ * Base class for 3rd party layers.  Create a new event pane layer with the
+ * <OpenLayers.Layer.EventPane> constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.EventPane = OpenLayers.Class(OpenLayers.Layer, {
+    
+    /**
+     * APIProperty: smoothDragPan
+     * {Boolean} smoothDragPan determines whether non-public/internal API
+     *     methods are used for better performance while dragging EventPane 
+     *     layers. When not in sphericalMercator mode, the smoother dragging 
+     *     doesn't actually move north/south directly with the number of 
+     *     pixels moved, resulting in a slight offset when you drag your mouse 
+     *     north south with this option on. If this visual disparity bothers 
+     *     you, you should turn this option off, or use spherical mercator. 
+     *     Default is on.
+     */
+    smoothDragPan: true,
+
+    /**
+     * Property: isBaseLayer
+     * {Boolean} EventPaned layers are always base layers, by necessity.
+     */ 
+    isBaseLayer: true,
+
+    /**
+     * APIProperty: isFixed
+     * {Boolean} EventPaned layers are fixed by default.
+     */ 
+    isFixed: true,
+
+    /**
+     * Property: pane
+     * {DOMElement} A reference to the element that controls the events.
+     */
+    pane: null,
+
+
+    /**
+     * Property: mapObject
+     * {Object} This is the object which will be used to load the 3rd party library
+     * in the case of the google layer, this will be of type GMap, 
+     * in the case of the ve layer, this will be of type VEMap
+     */ 
+    mapObject: null,
+
+
+    /**
+     * Constructor: OpenLayers.Layer.EventPane
+     * Create a new event pane layer
+     *
+     * Parameters:
+     * name - {String}
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+        if (this.pane == null) {
+            this.pane = OpenLayers.Util.createDiv(this.div.id + "_EventPane");
+        }
+    },
+    
+    /**
+     * APIMethod: destroy
+     * Deconstruct this layer.
+     */
+    destroy: function() {
+        this.mapObject = null;
+        OpenLayers.Layer.prototype.destroy.apply(this, arguments); 
+    },
+
+    
+    /**
+     * Method: setMap
+     * Set the map property for the layer. This is done through an accessor
+     * so that subclasses can override this and take special action once 
+     * they have their map variable set. 
+     *
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    setMap: function(map) {
+        OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+        
+        this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
+        this.pane.style.display = this.div.style.display;
+        this.pane.style.width="100%";
+        this.pane.style.height="100%";
+        if (OpenLayers.Util.getBrowserName() == "msie") {
+            this.pane.style.background = 
+                "url(" + OpenLayers.Util.getImagesLocation() + "blank.gif)";
+        }
+
+        if (this.isFixed) {
+            this.map.viewPortDiv.appendChild(this.pane);
+        } else {
+            this.map.layerContainerDiv.appendChild(this.pane);
+        }
+
+        // once our layer has been added to the map, we can load it
+        this.loadMapObject();
+    
+        // if map didn't load, display warning
+        if (this.mapObject == null) {
+            this.loadWarningMessage();
+        }
+    },
+
+    /**
+     * APIMethod: removeMap
+     * On being removed from the map, we'll like to remove the invisible 'pane'
+     *     div that we added to it on creation. 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    removeMap: function(map) {
+        if (this.pane && this.pane.parentNode) {
+            this.pane.parentNode.removeChild(this.pane);
+            this.pane = null;
+        }
+        OpenLayers.Layer.prototype.removeMap.apply(this, arguments);
+    },
+  
+    /**
+     * Method: loadWarningMessage
+     * If we can't load the map lib, then display an error message to the 
+     *     user and tell them where to go for help.
+     * 
+     *     This function sets up the layout for the warning message. Each 3rd
+     *     party layer must implement its own getWarningHTML() function to 
+     *     provide the actual warning message.
+     */
+    loadWarningMessage:function() {
+
+        this.div.style.backgroundColor = "darkblue";
+
+        var viewSize = this.map.getSize();
+        
+        var msgW = Math.min(viewSize.w, 300);
+        var msgH = Math.min(viewSize.h, 200);
+        var size = new OpenLayers.Size(msgW, msgH);
+
+        var centerPx = new OpenLayers.Pixel(viewSize.w/2, viewSize.h/2);
+
+        var topLeft = centerPx.add(-size.w/2, -size.h/2);            
+
+        var div = OpenLayers.Util.createDiv(this.name + "_warning", 
+                                            topLeft, 
+                                            size,
+                                            null,
+                                            null,
+                                            null,
+                                            "auto");
+
+        div.style.padding = "7px";
+        div.style.backgroundColor = "yellow";
+
+        div.innerHTML = this.getWarningHTML();
+        this.div.appendChild(div);
+    },
+  
+    /** 
+     * Method: getWarningHTML
+     * To be implemented by subclasses.
+     * 
+     * Returns:
+     * {String} String with information on why layer is broken, how to get
+     *          it working.
+     */
+    getWarningHTML:function() {
+        //should be implemented by subclasses
+        return "";
+    },
+  
+    /**
+     * Method: display
+     * Set the display on the pane
+     *
+     * Parameters:
+     * display - {Boolean}
+     */
+    display: function(display) {
+        OpenLayers.Layer.prototype.display.apply(this, arguments);
+        this.pane.style.display = this.div.style.display;
+    },
+  
+    /**
+     * Method: setZIndex
+     * Set the z-index order for the pane.
+     * 
+     * Parameters:
+     * zIndex - {int}
+     */
+    setZIndex: function (zIndex) {
+        OpenLayers.Layer.prototype.setZIndex.apply(this, arguments);
+        this.pane.style.zIndex = parseInt(this.div.style.zIndex) + 1;
+    },
+
+    /**
+     * Method: moveTo
+     * Handle calls to move the layer.
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * zoomChanged - {Boolean}
+     * dragging - {Boolean}
+     */
+    moveTo:function(bounds, zoomChanged, dragging) {
+        OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+        if (this.mapObject != null) {
+
+            var newCenter = this.map.getCenter();
+            var newZoom = this.map.getZoom();
+
+            if (newCenter != null) {
+
+                var moOldCenter = this.getMapObjectCenter();
+                var oldCenter = this.getOLLonLatFromMapObjectLonLat(moOldCenter);
+
+                var moOldZoom = this.getMapObjectZoom();
+                var oldZoom= this.getOLZoomFromMapObjectZoom(moOldZoom);
+
+                if ( !(newCenter.equals(oldCenter)) || 
+                     !(newZoom == oldZoom) ) {
+
+                    if (dragging && this.dragPanMapObject && 
+                        this.smoothDragPan) {
+                        var oldPx = this.map.getViewPortPxFromLonLat(oldCenter);
+                        var newPx = this.map.getViewPortPxFromLonLat(newCenter);
+                        this.dragPanMapObject(newPx.x-oldPx.x, oldPx.y-newPx.y);
+                    } else {
+                        var center = this.getMapObjectLonLatFromOLLonLat(newCenter);
+                        var zoom = this.getMapObjectZoomFromOLZoom(newZoom);
+                        this.setMapObjectCenter(center, zoom, dragging);
+                    }
+                }
+            }
+        }
+    },
+
+
+  /********************************************************/
+  /*                                                      */
+  /*                 Baselayer Functions                  */
+  /*                                                      */
+  /********************************************************/
+
+    /**
+     * Method: getLonLatFromViewPortPx
+     * Get a map location from a pixel location
+     * 
+     * Parameters:
+     * viewPortPx - {<OpenLayers.Pixel>}
+     *
+     * Returns:
+     *  {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
+     *  port OpenLayers.Pixel, translated into lon/lat by map lib
+     *  If the map lib is not loaded or not centered, returns null
+     */
+    getLonLatFromViewPortPx: function (viewPortPx) {
+        var lonlat = null;
+        if ( (this.mapObject != null) && 
+             (this.getMapObjectCenter() != null) ) {
+            var moPixel = this.getMapObjectPixelFromOLPixel(viewPortPx);
+            var moLonLat = this.getMapObjectLonLatFromMapObjectPixel(moPixel);
+            lonlat = this.getOLLonLatFromMapObjectLonLat(moLonLat);
+        }
+        return lonlat;
+    },
+
+ 
+    /**
+     * Method: getViewPortPxFromLonLat
+     * Get a pixel location from a map location
+     *
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>}
+     *
+     * Returns:
+     * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
+     * OpenLayers.LonLat, translated into view port pixels by map lib
+     * If map lib is not loaded or not centered, returns null
+     */
+    getViewPortPxFromLonLat: function (lonlat) {
+        var viewPortPx = null;
+        if ( (this.mapObject != null) && 
+             (this.getMapObjectCenter() != null) ) {
+
+            var moLonLat = this.getMapObjectLonLatFromOLLonLat(lonlat);
+            var moPixel = this.getMapObjectPixelFromMapObjectLonLat(moLonLat);
+        
+            viewPortPx = this.getOLPixelFromMapObjectPixel(moPixel);
+        }
+        return viewPortPx;
+    },
+
+  /********************************************************/
+  /*                                                      */
+  /*               Translation Functions                  */
+  /*                                                      */
+  /*   The following functions translate Map Object and   */
+  /*            OL formats for Pixel, LonLat              */
+  /*                                                      */
+  /********************************************************/
+
+  //
+  // TRANSLATION: MapObject LatLng <-> OpenLayers.LonLat
+  //
+
+    /**
+     * Method: getOLLonLatFromMapObjectLonLat
+     * Get an OL style map location from a 3rd party style map location
+     *
+     * Parameters
+     * moLonLat - {Object}
+     * 
+     * Returns:
+     * {<OpenLayers.LonLat>} An OpenLayers.LonLat, translated from the passed in 
+     *          MapObject LonLat
+     *          Returns null if null value is passed in
+     */
+    getOLLonLatFromMapObjectLonLat: function(moLonLat) {
+        var olLonLat = null;
+        if (moLonLat != null) {
+            var lon = this.getLongitudeFromMapObjectLonLat(moLonLat);
+            var lat = this.getLatitudeFromMapObjectLonLat(moLonLat);
+            olLonLat = new OpenLayers.LonLat(lon, lat);
+        }
+        return olLonLat;
+    },
+
+    /**
+     * Method: getMapObjectLonLatFromOLLonLat
+     * Get a 3rd party map location from an OL map location.
+     *
+     * Parameters:
+     * olLonLat - {<OpenLayers.LonLat>}
+     * 
+     * Returns:
+     * {Object} A MapObject LonLat, translated from the passed in 
+     *          OpenLayers.LonLat
+     *          Returns null if null value is passed in
+     */
+    getMapObjectLonLatFromOLLonLat: function(olLonLat) {
+        var moLatLng = null;
+        if (olLonLat != null) {
+            moLatLng = this.getMapObjectLonLatFromLonLat(olLonLat.lon,
+                                                         olLonLat.lat);
+        }
+        return moLatLng;
+    },
+
+
+  //
+  // TRANSLATION: MapObject Pixel <-> OpenLayers.Pixel
+  //
+
+    /**
+     * Method: getOLPixelFromMapObjectPixel
+     * Get an OL pixel location from a 3rd party pixel location.
+     *
+     * Parameters:
+     * moPixel - {Object}
+     * 
+     * Returns:
+     * {<OpenLayers.Pixel>} An OpenLayers.Pixel, translated from the passed in 
+     *          MapObject Pixel
+     *          Returns null if null value is passed in
+     */
+    getOLPixelFromMapObjectPixel: function(moPixel) {
+        var olPixel = null;
+        if (moPixel != null) {
+            var x = this.getXFromMapObjectPixel(moPixel);
+            var y = this.getYFromMapObjectPixel(moPixel);
+            olPixel = new OpenLayers.Pixel(x, y);
+        }
+        return olPixel;
+    },
+
+    /**
+     * Method: getMapObjectPixelFromOLPixel
+     * Get a 3rd party pixel location from an OL pixel location
+     *
+     * Parameters:
+     * olPixel - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {Object} A MapObject Pixel, translated from the passed in 
+     *          OpenLayers.Pixel
+     *          Returns null if null value is passed in
+     */
+    getMapObjectPixelFromOLPixel: function(olPixel) {
+        var moPixel = null;
+        if (olPixel != null) {
+            moPixel = this.getMapObjectPixelFromXY(olPixel.x, olPixel.y);
+        }
+        return moPixel;
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.EventPane"
+});
+/* ======================================================================
+    OpenLayers/Layer/FixedZoomLevels.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.FixedZoomLevels
+ *   Some Layers will already have established zoom levels (like google 
+ *    or ve). Instead of trying to determine them and populate a resolutions[]
+ *    Array with those values, we will hijack the resolution functionality
+ *    here.
+ * 
+ *   When you subclass FixedZoomLevels: 
+ * 
+ *   The initResolutions() call gets nullified, meaning no resolutions[] array 
+ *    is set up. Which would be a big problem getResolution() in Layer, since 
+ *    it merely takes map.zoom and indexes into resolutions[]... but....
+ * 
+ *   The getResolution() call is also overridden. Instead of using the 
+ *    resolutions[] array, we simply calculate the current resolution based
+ *    on the current extent and the current map size. But how will we be able
+ *    to calculate the current extent without knowing the resolution...?
+ *  
+ *   The getExtent() function is also overridden. Instead of calculating extent
+ *    based on the center point and the current resolution, we instead 
+ *    calculate the extent by getting the lonlats at the top-left and 
+ *    bottom-right by using the getLonLatFromViewPortPx() translation function,
+ *    taken from the pixel locations (0,0) and the size of the map. But how 
+ *    will we be able to do lonlat-px translation without resolution....?
+ * 
+ *   The getZoomForResolution() method is overridden. Instead of indexing into
+ *    the resolutions[] array, we call OpenLayers.Layer.getExent(), passing in
+ *    the desired resolution. With this extent, we then call getZoomForExtent() 
+ * 
+ * 
+ *   Whenever you implement a layer using OpenLayers.Layer.FixedZoomLevels, 
+ *    it is your responsibility to provide the following three functions:
+ * 
+ *   - getLonLatFromViewPortPx
+ *   - getViewPortPxFromLonLat
+ *   - getZoomForExtent
+ * 
+ *  ...those three functions should generally be provided by any reasonable 
+ *  API that you might be working from.
+ *
+ */
+OpenLayers.Layer.FixedZoomLevels = OpenLayers.Class({
+      
+  /********************************************************/
+  /*                                                      */
+  /*                 Baselayer Functions                  */
+  /*                                                      */
+  /*    The following functions must all be implemented   */
+  /*                  by all base layers                  */
+  /*                                                      */
+  /********************************************************/
+    
+    /**
+     * Constructor: OpenLayers.Layer.FixedZoomLevels
+     * Create a new fixed zoom levels layer.
+     */
+    initialize: function() {
+        //this class is only just to add the following functions... 
+        // nothing to actually do here... but it is probably a good
+        // idea to have layers that use these functions call this 
+        // inititalize() anyways, in case at some point we decide we 
+        // do want to put some functionality or state in here. 
+    },
+    
+    /**
+     * Method: initResolutions
+     * Populate the resolutions array
+     */
+    initResolutions: function() {
+
+        var props = new Array('minZoomLevel', 'maxZoomLevel', 'numZoomLevels');
+          
+        for(var i=0, len=props.length; i<len; i++) {
+            var property = props[i];
+            this[property] = (this.options[property] != null)  
+                                     ? this.options[property] 
+                                     : this.map[property];
+        }
+
+        if ( (this.minZoomLevel == null) ||
+             (this.minZoomLevel < this.MIN_ZOOM_LEVEL) ){
+            this.minZoomLevel = this.MIN_ZOOM_LEVEL;
+        }        
+
+        var limitZoomLevels = this.MAX_ZOOM_LEVEL - this.minZoomLevel + 1;
+        if (this.numZoomLevels != null) {
+            this.numZoomLevels = Math.min(this.numZoomLevels, limitZoomLevels);
+        } else {
+            if (this.maxZoomLevel != null) {
+                var zoomDiff = this.maxZoomLevel - this.minZoomLevel + 1;
+                this.numZoomLevels = Math.min(zoomDiff, limitZoomLevels);
+            } else {
+                this.numZoomLevels = limitZoomLevels;
+            }
+        }
+
+        this.maxZoomLevel = this.minZoomLevel + this.numZoomLevels - 1;
+
+        if (this.RESOLUTIONS != null) {
+            var resolutionsIndex = 0;
+            this.resolutions = [];
+            for(var i= this.minZoomLevel; i <= this.maxZoomLevel; i++) {
+                this.resolutions[resolutionsIndex++] = this.RESOLUTIONS[i];            
+            }
+            this.maxResolution = this.resolutions[0];
+            this.minResolution = this.resolutions[this.resolutions.length - 1];
+        }       
+    },
+    
+    /**
+     * APIMethod: getResolution
+     * Get the current map resolution
+     * 
+     * Returns:
+     * {Float} Map units per Pixel
+     */
+    getResolution: function() {
+
+        if (this.resolutions != null) {
+            return OpenLayers.Layer.prototype.getResolution.apply(this, arguments);
+        } else {
+            var resolution = null;
+            
+            var viewSize = this.map.getSize();
+            var extent = this.getExtent();
+            
+            if ((viewSize != null) && (extent != null)) {
+                resolution = Math.max( extent.getWidth()  / viewSize.w,
+                                       extent.getHeight() / viewSize.h );
+            }
+            return resolution;
+        }
+     },
+
+    /**
+     * APIMethod: getExtent
+     * Calculates using px-> lonlat translation functions on tl and br 
+     *     corners of viewport
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat 
+     *                       bounds of the current viewPort.
+     */
+    getExtent: function () {
+        var extent = null;
+        
+        
+        var size = this.map.getSize();
+        
+        var tlPx = new OpenLayers.Pixel(0,0);
+        var tlLL = this.getLonLatFromViewPortPx(tlPx);
+
+        var brPx = new OpenLayers.Pixel(size.w, size.h);
+        var brLL = this.getLonLatFromViewPortPx(brPx);
+        
+        if ((tlLL != null) && (brLL != null)) {
+            extent = new OpenLayers.Bounds(tlLL.lon, 
+                                       brLL.lat, 
+                                       brLL.lon, 
+                                       tlLL.lat);
+        }
+
+        return extent;
+    },
+
+    /**
+     * Method: getZoomForResolution
+     * Get the zoom level for a given resolution
+     *
+     * Parameters:
+     * resolution - {Float}
+     *
+     * Returns:
+     * {Integer} A suitable zoom level for the specified resolution.
+     *           If no baselayer is set, returns null.
+     */
+    getZoomForResolution: function(resolution) {
+      
+        if (this.resolutions != null) {
+            return OpenLayers.Layer.prototype.getZoomForResolution.apply(this, arguments);
+        } else {
+            var extent = OpenLayers.Layer.prototype.getExtent.apply(this, []);
+            return this.getZoomForExtent(extent);
+        }
+    },
+
+
+
+    
+    /********************************************************/
+    /*                                                      */
+    /*             Translation Functions                    */
+    /*                                                      */
+    /*    The following functions translate GMaps and OL    */ 
+    /*     formats for Pixel, LonLat, Bounds, and Zoom      */
+    /*                                                      */
+    /********************************************************/
+    
+    
+    //
+    // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom
+    //
+  
+    /**
+     * Method: getOLZoomFromMapObjectZoom
+     * Get the OL zoom index from the map object zoom level
+     *
+     * Parameters:
+     * moZoom - {Integer}
+     * 
+     * Returns:
+     * {Integer} An OpenLayers Zoom level, translated from the passed in zoom
+     *           Returns null if null value is passed in
+     */
+    getOLZoomFromMapObjectZoom: function(moZoom) {
+        var zoom = null;
+        if (moZoom != null) {
+            zoom = moZoom - this.minZoomLevel;
+        }
+        return zoom;
+    },
+    
+    /**
+     * Method: getMapObjectZoomFromOLZoom
+     * Get the map object zoom level from the OL zoom level
+     *
+     * Parameters:
+     * olZoom - {Integer}
+     * 
+     * Returns:
+     * {Integer} A MapObject level, translated from the passed in olZoom
+     *           Returns null if null value is passed in
+     */
+    getMapObjectZoomFromOLZoom: function(olZoom) {
+        var zoom = null; 
+        if (olZoom != null) {
+            zoom = olZoom + this.minZoomLevel;
+        }
+        return zoom;
+    },
+
+    CLASS_NAME: "FixedZoomLevels.js"
+});
+
+/* ======================================================================
     OpenLayers/Layer/HTTPRequest.js
    ====================================================================== */
 
@@ -13769,7 +29303,7 @@
      */
     selectUrl: function(paramString, urls) {
         var product = 1;
-        for (var i = 0; i < paramString.length; i++) { 
+        for (var i=0, len=paramString.length; i<len; i++) { 
             product *= paramString.charCodeAt(i) * this.URL_HASH_FACTOR; 
             product -= Math.floor(product); 
         }
@@ -13848,6 +29382,1072 @@
     CLASS_NAME: "OpenLayers.Layer.HTTPRequest"
 });
 /* ======================================================================
+    OpenLayers/Layer/Image.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+ 
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Tile/Image.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Image
+ * Instances of OpenLayers.Layer.Image are used to display data from a web
+ * accessible image as a map layer.  Create a new image layer with the
+ * <OpenLayers.Layer.Image> constructor.  Inherits from <OpenLayers.Layer>.
+ */
+OpenLayers.Layer.Image = OpenLayers.Class(OpenLayers.Layer, {
+
+    /**
+     * Property: isBaseLayer
+     * {Boolean} The layer is a base layer.  Default is true.  Set this property
+     * in the layer options
+     */
+    isBaseLayer: true,
+    
+    /**
+     * Property: url
+     * {String} URL of the image to use
+     */
+    url: null,
+
+    /**
+     * Property: extent
+     * {<OpenLayers.Bounds>} The image bounds in map units
+     */
+    extent: null,
+    
+    /**
+     * Property: size
+     * {<OpenLayers.Size>} The image size in pixels
+     */
+    size: null,
+
+    /**
+     * Property: tile
+     * {<OpenLayers.Tile.Image>}
+     */
+    tile: null,
+
+    /**
+     * Property: aspectRatio
+     * {Float} The ratio of height/width represented by a single pixel in the
+     * graphic
+     */
+    aspectRatio: null,
+
+    /**
+     * Constructor: OpenLayers.Layer.Image
+     * Create a new image layer
+     *
+     * Parameters:
+     * name - {String} A name for the layer.
+     * url - {String} Relative or absolute path to the image
+     * extent - {<OpenLayers.Bounds>} The extent represented by the image
+     * size - {<OpenLayers.Size>} The size (in pixels) of the image
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, url, extent, size, options) {
+        this.url = url;
+        this.extent = extent;
+        this.size = size;
+        OpenLayers.Layer.prototype.initialize.apply(this, [name, options]);
+
+        this.aspectRatio = (this.extent.getHeight() / this.size.h) /
+                           (this.extent.getWidth() / this.size.w);
+    },    
+
+    /**
+     * Method: destroy
+     * Destroy this layer
+     */
+    destroy: function() {
+        if (this.tile) {
+            this.tile.destroy();
+            this.tile = null;
+        }
+        OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+    },
+    
+    /**
+     * Method: clone
+     * Create a clone of this layer
+     *
+     * Paramters:
+     * obj - {Object} An optional layer (is this ever used?)
+     *
+     * Returns:
+     * {<OpenLayers.Layer.Image>} An exact copy of this layer
+     */
+    clone: function(obj) {
+        
+        if(obj == null) {
+            obj = new OpenLayers.Layer.Image(this.name,
+                                               this.url,
+                                               this.extent,
+                                               this.size,
+                                               this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    },    
+    
+    /**
+     * APIMethod: setMap
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    setMap: function(map) {
+        /**
+         * If nothing to do with resolutions has been set, assume a single
+         * resolution determined by ratio*extent/size - if an image has a
+         * pixel aspect ratio different than one (as calculated above), the
+         * image will be stretched in one dimension only.
+         */
+        if( this.options.maxResolution == null ) {
+            this.options.maxResolution = this.aspectRatio *
+                                         this.extent.getWidth() /
+                                         this.size.w;
+        }
+        OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+    },
+
+    /** 
+     * Method: moveTo
+     * Create the tile for the image or resize it for the new resolution
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * zoomChanged - {Boolean}
+     * dragging - {Boolean}
+     */
+    moveTo:function(bounds, zoomChanged, dragging) {
+        OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+        var firstRendering = (this.tile == null);
+
+        if(zoomChanged || firstRendering) {
+
+            //determine new tile size
+            this.setTileSize();
+
+            //determine new position (upper left corner of new bounds)
+            var ul = new OpenLayers.LonLat(this.extent.left, this.extent.top);
+            var ulPx = this.map.getLayerPxFromLonLat(ul);
+
+            if(firstRendering) {
+                //create the new tile
+                this.tile = new OpenLayers.Tile.Image(this, ulPx, this.extent, 
+                                                      null, this.tileSize);
+            } else {
+                //just resize the tile and set it's new position
+                this.tile.size = this.tileSize.clone();
+                this.tile.position = ulPx.clone();
+            }
+            this.tile.draw();
+        }
+    }, 
+
+    /**
+     * Set the tile size based on the map size.
+     */
+    setTileSize: function() {
+        var tileWidth = this.extent.getWidth() / this.map.getResolution();
+        var tileHeight = this.extent.getHeight() / this.map.getResolution();
+        this.tileSize = new OpenLayers.Size(tileWidth, tileHeight);
+    },
+
+    /**
+     * APIMethod: setUrl
+     * 
+     * Parameters:
+     * newUrl - {String}
+     */
+    setUrl: function(newUrl) {
+        this.url = newUrl;
+        this.tile.draw();
+    },
+
+    /** 
+     * APIMethod: getURL
+     * The url we return is always the same (the image itself never changes)
+     *     so we can ignore the bounds parameter (it will always be the same, 
+     *     anyways) 
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     */
+    getURL: function(bounds) {
+        return this.url;
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Image"
+});
+/* ======================================================================
+    OpenLayers/Layer/Markers.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Markers
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer> 
+ */
+OpenLayers.Layer.Markers = OpenLayers.Class(OpenLayers.Layer, {
+    
+    /** 
+     * APIProperty: isBaseLayer 
+     * {Boolean} Markers layer is never a base layer.  
+     */
+    isBaseLayer: false,
+    
+    /** 
+     * Property: markers 
+     * {Array(<OpenLayers.Marker>)} internal marker list 
+     */
+    markers: null,
+
+
+    /** 
+     * Property: drawn 
+     * {Boolean} internal state of drawing. This is a workaround for the fact
+     * that the map does not call moveTo with a zoomChanged when the map is
+     * first starting up. This lets us catch the case where we have *never*
+     * drawn the layer, and draw it even if the zoom hasn't changed.
+     */
+    drawn: false,
+    
+    /**
+     * Constructor: OpenLayers.Layer.Markers 
+     * Create a Markers layer.
+     *
+     * Parameters:
+     * name - {String} 
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+        this.markers = [];
+    },
+    
+    /**
+     * APIMethod: destroy 
+     */
+    destroy: function() {
+        this.clearMarkers();
+        this.markers = null;
+        OpenLayers.Layer.prototype.destroy.apply(this, arguments);
+    },
+
+    /**
+     * APIMethod: setOpacity
+     * Sets the opacity for all the markers.
+     * 
+     * Parameter:
+     * opacity - {Float}
+     */
+    setOpacity: function(opacity) {
+        if (opacity != this.opacity) {
+            this.opacity = opacity;
+            for (var i=0, len=this.markers.length; i<len; i++) {
+                this.markers[i].setOpacity(this.opacity);
+            }
+        }
+    },
+
+    /** 
+     * Method: moveTo
+     *
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     * zoomChanged - {Boolean} 
+     * dragging - {Boolean} 
+     */
+    moveTo:function(bounds, zoomChanged, dragging) {
+        OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+
+        if (zoomChanged || !this.drawn) {
+            for(var i=0, len=this.markers.length; i<len; i++) {
+                this.drawMarker(this.markers[i]);
+            }
+            this.drawn = true;
+        }
+    },
+
+    /**
+     * APIMethod: addMarker
+     *
+     * Parameters:
+     * marker - {<OpenLayers.Marker>} 
+     */
+    addMarker: function(marker) {
+        this.markers.push(marker);
+
+        if (this.opacity != null) {
+            marker.setOpacity(this.opacity);
+        }
+
+        if (this.map && this.map.getExtent()) {
+            marker.map = this.map;
+            this.drawMarker(marker);
+        }
+    },
+
+    /**
+     * APIMethod: removeMarker
+     *
+     * Parameters:
+     * marker - {<OpenLayers.Marker>} 
+     */
+    removeMarker: function(marker) {
+        if (this.markers && this.markers.length) {
+            OpenLayers.Util.removeItem(this.markers, marker);
+            if ((marker.icon != null) && (marker.icon.imageDiv != null) &&
+                (marker.icon.imageDiv.parentNode == this.div) ) {
+                this.div.removeChild(marker.icon.imageDiv);    
+                marker.drawn = false;
+            }
+        }
+    },
+
+    /**
+     * Method: clearMarkers
+     * This method removes all markers from a layer. The markers are not
+     * destroyed by this function, but are removed from the list of markers.
+     */
+    clearMarkers: function() {
+        if (this.markers != null) {
+            while(this.markers.length > 0) {
+                this.removeMarker(this.markers[0]);
+            }
+        }
+    },
+
+    /** 
+     * Method: drawMarker
+     * Calculate the pixel location for the marker, create it, and 
+     *    add it to the layer's div
+     *
+     * Parameters:
+     * marker - {<OpenLayers.Marker>} 
+     */
+    drawMarker: function(marker) {
+        var px = this.map.getLayerPxFromLonLat(marker.lonlat);
+        if (px == null) {
+            marker.display(false);
+        } else {
+            var markerImg = marker.draw(px);
+            if (!marker.drawn) {
+                this.div.appendChild(markerImg);
+                marker.drawn = true;
+            }
+        }
+    },
+    
+    /** 
+     * APIMethod: getDataExtent
+     * Calculates the max extent which includes all of the markers.
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>}
+     */
+    getDataExtent: function () {
+        var maxExtent = null;
+        
+        if ( this.markers && (this.markers.length > 0)) {
+            var maxExtent = new OpenLayers.Bounds();
+            for(var i=0, len=this.markers.length; i<len; i++) {
+                var marker = this.markers[i];
+                maxExtent.extend(marker.lonlat);
+            }
+        }
+
+        return maxExtent;
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Markers"
+});
+/* ======================================================================
+    OpenLayers/Layer/SphericalMercator.js
+   ====================================================================== */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.SphericalMercator
+ * A mixin for layers that wraps up the pieces neccesary to have a coordinate
+ *     conversion for working with commercial APIs which use a spherical
+ *     mercator projection.  Using this layer as a base layer, additional
+ *     layers can be used as overlays if they are in the same projection.
+ *
+ * A layer is given properties of this object by setting the sphericalMercator
+ *     property to true.
+ *
+ * More projection information:
+ *  - http://spatialreference.org/ref/user/google-projection/
+ *
+ * Proj4 Text:
+ *     +proj=merc +a=6378137 +b=6378137 +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0
+ *     +k=1.0 +units=m +nadgrids=@null +no_defs
+ *
+ * WKT:
+ *     900913=PROJCS["WGS84 / Simple Mercator", GEOGCS["WGS 84",
+ *     DATUM["WGS_1984", SPHEROID["WGS_1984", 6378137.0, 298.257223563]], 
+ *     PRIMEM["Greenwich", 0.0], UNIT["degree", 0.017453292519943295], 
+ *     AXIS["Longitude", EAST], AXIS["Latitude", NORTH]],
+ *     PROJECTION["Mercator_1SP_Google"], 
+ *     PARAMETER["latitude_of_origin", 0.0], PARAMETER["central_meridian", 0.0], 
+ *     PARAMETER["scale_factor", 1.0], PARAMETER["false_easting", 0.0], 
+ *     PARAMETER["false_northing", 0.0], UNIT["m", 1.0], AXIS["x", EAST],
+ *     AXIS["y", NORTH], AUTHORITY["EPSG","900913"]]
+ */
+OpenLayers.Layer.SphericalMercator = {
+
+    /**
+     * Method: getExtent
+     * Get the map's extent.
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>} The map extent.
+     */
+    getExtent: function() {
+        var extent = null;
+        if (this.sphericalMercator) {
+            extent = this.map.calculateBounds();
+        } else {
+            extent = OpenLayers.Layer.FixedZoomLevels.prototype.getExtent.apply(this);
+        }
+        return extent;
+    },
+
+    /** 
+     * Method: initMercatorParameters 
+     * Set up the mercator parameters on the layer: resolutions,
+     *     projection, units.
+     */
+    initMercatorParameters: function() {
+        // set up properties for Mercator - assume EPSG:900913
+        this.RESOLUTIONS = [];
+        var maxResolution = 156543.0339;
+        for(var zoom=0; zoom<=this.MAX_ZOOM_LEVEL; ++zoom) {
+            this.RESOLUTIONS[zoom] = maxResolution / Math.pow(2, zoom);
+        }
+        this.units = "m";
+        this.projection = "EPSG:900913";
+    },
+
+    /**
+     * APIMethod: forwardMercator
+     * Given a lon,lat in EPSG:4326, return a point in Spherical Mercator.
+     *
+     * Parameters:
+     * lon - {float} 
+     * lat - {float}
+     * 
+     * Returns:
+     * {<OpenLayers.LonLat>} The coordinates transformed to Mercator.
+     */
+    forwardMercator: function(lon, lat) {
+        var x = lon * 20037508.34 / 180;
+        var y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
+
+        y = y * 20037508.34 / 180;
+        
+        return new OpenLayers.LonLat(x, y);
+    },
+
+    /**
+     * APIMethod: inverseMercator
+     * Given a x,y in Spherical Mercator, return a point in EPSG:4326.
+     *
+     * Parameters:
+     * x - {float} A map x in Spherical Mercator.
+     * y - {float} A map y in Spherical Mercator.
+     * 
+     * Returns:
+     * {<OpenLayers.LonLat>} The coordinates transformed to EPSG:4326.
+     */
+    inverseMercator: function(x, y) {
+
+        var lon = (x / 20037508.34) * 180;
+        var lat = (y / 20037508.34) * 180;
+
+        lat = 180/Math.PI * (2 * Math.atan(Math.exp(lat * Math.PI / 180)) - Math.PI / 2);
+        
+        return new OpenLayers.LonLat(lon, lat);
+    },
+
+    /**
+     * Method: projectForward 
+     * Given an object with x and y properties in EPSG:4326, modify the x,y
+     * properties on the object to be the Spherical Mercator projected
+     * coordinates.
+     *
+     * Parameters:
+     * point - {Object} An object with x and y properties. 
+     * 
+     * Returns:
+     * {Object} The point, with the x and y properties transformed to spherical
+     * mercator.
+     */
+    projectForward: function(point) {
+        var lonlat = OpenLayers.Layer.SphericalMercator.forwardMercator(point.x, point.y);
+        point.x = lonlat.lon;
+        point.y = lonlat.lat;
+        return point;
+    },
+    
+    /**
+     * Method: projectInverse
+     * Given an object with x and y properties in Spherical Mercator, modify
+     * the x,y properties on the object to be the unprojected coordinates.
+     *
+     * Parameters:
+     * point - {Object} An object with x and y properties. 
+     * 
+     * Returns:
+     * {Object} The point, with the x and y properties transformed from
+     * spherical mercator to unprojected coordinates..
+     */
+    projectInverse: function(point) {
+        var lonlat = OpenLayers.Layer.SphericalMercator.inverseMercator(point.x, point.y);
+        point.x = lonlat.lon;
+        point.y = lonlat.lat;
+        return point;
+    }
+
+};
+
+/**
+ * Note: Two transforms declared
+ * Transforms from EPSG:4326 to EPSG:900913 and from EPSG:900913 to EPSG:4326
+ *     are set by this class.
+ */
+OpenLayers.Projection.addTransform("EPSG:4326", "EPSG:900913",
+    OpenLayers.Layer.SphericalMercator.projectForward);
+OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:4326",
+    OpenLayers.Layer.SphericalMercator.projectInverse);
+/* ======================================================================
+    OpenLayers/Control/DrawFeature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Control.DrawFeature
+ * Draws features on a vector layer when active.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.DrawFeature = OpenLayers.Class(OpenLayers.Control, {
+    
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>}
+     */
+    layer: null,
+
+    /**
+     * Property: callbacks
+     * {Object} The functions that are sent to the handler for callback
+     */
+    callbacks: null,
+    
+    /**
+     * Constant: EVENT_TYPES
+     *
+     * Supported event types:
+     *  - *featureadded* Triggered when a feature is added
+     */
+    EVENT_TYPES: ["featureadded"],
+    
+    /**
+     * APIProperty: featureAdded
+     * {Function} Called after each feature is added
+     */
+    featureAdded: function() {},
+
+    /**
+     * APIProperty: handlerOptions
+     * {Object} Used to set non-default properties on the control's handler
+     */
+    handlerOptions: null,
+    
+    /**
+     * Constructor: OpenLayers.Control.DrawFeature
+     * 
+     * Parameters:
+     * layer - {<OpenLayers.Layer.Vector>} 
+     * handler - {<OpenLayers.Handler>} 
+     * options - {Object} 
+     */
+    initialize: function(layer, handler, options) {
+        
+        // concatenate events specific to vector with those from the base
+        this.EVENT_TYPES =
+            OpenLayers.Control.DrawFeature.prototype.EVENT_TYPES.concat(
+            OpenLayers.Control.prototype.EVENT_TYPES
+        );
+        
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.callbacks = OpenLayers.Util.extend({done: this.drawFeature},
+                                                this.callbacks);
+        this.layer = layer;
+        this.handler = new handler(this, this.callbacks, this.handlerOptions);
+    },
+
+    /**
+     * Method: drawFeature
+     */
+    drawFeature: function(geometry) {
+        var feature = new OpenLayers.Feature.Vector(geometry);
+        this.layer.addFeatures([feature]);
+        this.featureAdded(feature);
+        this.events.triggerEvent("featureadded",{feature : feature});
+    },
+
+    CLASS_NAME: "OpenLayers.Control.DrawFeature"
+});
+/* ======================================================================
+    OpenLayers/Control/SelectFeature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Handler/Feature.js
+ */
+
+/**
+ * Class: OpenLayers.Control.SelectFeature
+ * Selects vector features from a given layer on click or hover. 
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.SelectFeature = OpenLayers.Class(OpenLayers.Control, {
+    
+    /**
+     * Property: multipleKey
+     * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+     *     the <multiple> property to true.  Default is null.
+     */
+    multipleKey: null,
+    
+    /**
+     * Property: toggleKey
+     * {String} An event modifier ('altKey' or 'shiftKey') that temporarily sets
+     *     the <toggle> property to true.  Default is null.
+     */
+    toggleKey: null,
+    
+    /**
+     * APIProperty: multiple
+     * {Boolean} Allow selection of multiple geometries.  Default is false.
+     */
+    multiple: false, 
+
+    /**
+     * APIProperty: clickout
+     * {Boolean} Unselect features when clicking outside any feature.
+     *     Default is true.
+     */
+    clickout: true,
+
+    /**
+     * APIProperty: toggle
+     * {Boolean} Unselect a selected feature on click.  Default is false.  Only
+     *     has meaning if hover is false.
+     */
+    toggle: false,
+
+    /**
+     * APIProperty: hover
+     * {Boolean} Select on mouse over and deselect on mouse out.  If true, this
+     * ignores clicks and only listens to mouse moves.
+     */
+    hover: false,
+    
+    /**
+     * APIProperty: box
+     * {Boolean} Allow feature selection by drawing a box.
+     */
+    box: false,
+    
+    /**
+     * APIProperty: onSelect 
+     * {Function} Optional function to be called when a feature is selected.
+     * The function should expect to be called with a feature.
+     */
+    onSelect: function() {},
+
+    /**
+     * APIProperty: onUnselect
+     * {Function} Optional function to be called when a feature is unselected.
+     *                  The function should expect to be called with a feature.
+     */
+    onUnselect: function() {},
+
+    /**
+     * APIProperty: geometryTypes
+     * {Array(String)} To restrict selecting to a limited set of geometry types,
+     *     send a list of strings corresponding to the geometry class names.
+     */
+    geometryTypes: null,
+
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>}
+     */
+    layer: null,
+    
+    /**
+     * APIProperty: callbacks
+     * {Object} The functions that are sent to the handlers.feature for callback
+     */
+    callbacks: null,
+    
+    /**
+     * APIProperty: selectStyle 
+     * {Object} Hash of styles
+     */
+    selectStyle: null,
+    
+    /**
+     * Property: renderIntent
+     * {String} key used to retrieve the select style from the layer's
+     * style map.
+     */
+    renderIntent: "select",
+
+    /**
+     * Property: handlers
+     * {Object} Object with references to multiple <OpenLayers.Handler>
+     *     instances.
+     */
+    handlers: null,
+
+    /**
+     * Constructor: <OpenLayers.Control.SelectFeature>
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.Vector>} 
+     * options - {Object} 
+     */
+    initialize: function(layer, options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.layer = layer;
+        var callbacks = {
+            click: this.clickFeature,
+            clickout: this.clickoutFeature
+        };
+        if (this.hover) {
+            callbacks.over = this.overFeature;
+            callbacks.out = this.outFeature;
+        }
+             
+        this.callbacks = OpenLayers.Util.extend(callbacks, this.callbacks);
+        this.handlers = {
+            feature: new OpenLayers.Handler.Feature(
+                this, layer, this.callbacks, {geometryTypes: this.geometryTypes}
+            )
+        };
+
+        if (this.box) {
+            this.handlers.box = new OpenLayers.Handler.Box(
+                this, {done: this.selectBox},
+                {boxDivClassName: "olHandlerBoxSelectFeature"}
+            ); 
+        }
+    },
+
+    /**
+     * Method: activate
+     * Activates the control.
+     * 
+     * Returns:
+     * {Boolean} The control was effectively activated.
+     */
+    activate: function () {
+        if (!this.active) {
+            this.handlers.feature.activate();
+            if(this.box && this.handlers.box) {
+                this.handlers.box.activate();
+            }
+        }
+        return OpenLayers.Control.prototype.activate.apply(
+            this, arguments
+        );
+    },
+
+    /**
+     * Method: deactivate
+     * Deactivates the control.
+     * 
+     * Returns:
+     * {Boolean} The control was effectively deactivated.
+     */
+    deactivate: function () {
+        if (this.active) {
+            this.handlers.feature.deactivate();
+            if(this.handlers.box) {
+                this.handlers.box.deactivate();
+            }
+        }
+        return OpenLayers.Control.prototype.deactivate.apply(
+            this, arguments
+        );
+    },
+
+    /**
+     * Method: unselectAll
+     * Unselect all selected features.  To unselect all except for a single
+     *     feature, set the options.except property to the feature.
+     *
+     * Parameters:
+     * options - {Object} Optional configuration object.
+     */
+    unselectAll: function(options) {
+        // we'll want an option to supress notification here
+        var feature;
+        for(var i=this.layer.selectedFeatures.length-1; i>=0; --i) {
+            feature = this.layer.selectedFeatures[i];
+            if(!options || options.except != feature) {
+                this.unselect(feature);
+            }
+        }
+    },
+
+    /**
+     * Method: clickFeature
+     * Called on click in a feature
+     * Only responds if this.hover is false.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    clickFeature: function(feature) {
+        if(!this.hover) {
+            var selected = (OpenLayers.Util.indexOf(this.layer.selectedFeatures,
+                                                    feature) > -1);
+            if(selected) {
+                if(this.toggleSelect()) {
+                    this.unselect(feature);
+                } else if(!this.multipleSelect()) {
+                    this.unselectAll({except: feature});
+                }
+            } else {
+                if(!this.multipleSelect()) {
+                    this.unselectAll({except: feature});
+                }
+                this.select(feature);
+            }
+        }
+    },
+
+    /**
+     * Method: multipleSelect
+     * Allow for multiple selected features based on <multiple> property and
+     *     <multipleKey> event modifier.
+     *
+     * Returns:
+     * {Boolean} Allow for multiple selected features.
+     */
+    multipleSelect: function() {
+        return this.multiple || this.handlers.feature.evt[this.multipleKey];
+    },
+    
+    /**
+     * Method: toggleSelect
+     * Event should toggle the selected state of a feature based on <toggle>
+     *     property and <toggleKey> event modifier.
+     *
+     * Returns:
+     * {Boolean} Toggle the selected state of a feature.
+     */
+    toggleSelect: function() {
+        return this.toggle || this.handlers.feature.evt[this.toggleKey];
+    },
+
+    /**
+     * Method: clickoutFeature
+     * Called on click outside a previously clicked (selected) feature.
+     * Only responds if this.hover is false.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Vector.Feature>} 
+     */
+    clickoutFeature: function(feature) {
+        if(!this.hover && this.clickout) {
+            this.unselectAll();
+        }
+    },
+
+    /**
+     * Method: overFeature
+     * Called on over a feature.
+     * Only responds if this.hover is true.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    overFeature: function(feature) {
+        if(this.hover &&
+           (OpenLayers.Util.indexOf(this.layer.selectedFeatures, feature) == -1)) {
+            this.select(feature);
+        }
+    },
+
+    /**
+     * Method: outFeature
+     * Called on out of a selected feature.
+     * Only responds if this.hover is true.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    outFeature: function(feature) {
+        if(this.hover) {
+            this.unselect(feature);
+        }
+    },
+    
+    /**
+     * Method: select
+     * Add feature to the layer's selectedFeature array, render the feature as
+     * selected, and call the onSelect function.
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    select: function(feature) {
+        var cont = this.layer.events.triggerEvent("beforefeatureselected", {
+            feature: feature
+        });
+        if(cont !== false) {
+            this.layer.selectedFeatures.push(feature);
+    
+            var selectStyle = this.selectStyle || this.renderIntent;
+            
+            this.layer.drawFeature(feature, selectStyle);
+            this.layer.events.triggerEvent("featureselected", {feature: feature});
+            this.onSelect(feature);
+        }
+    },
+
+    /**
+     * Method: unselect
+     * Remove feature from the layer's selectedFeature array, render the feature as
+     * normal, and call the onUnselect function.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     */
+    unselect: function(feature) {
+        // Store feature style for restoration later
+        this.layer.drawFeature(feature, "default");
+        OpenLayers.Util.removeItem(this.layer.selectedFeatures, feature);
+        this.layer.events.triggerEvent("featureunselected", {feature: feature});
+        this.onUnselect(feature);
+    },
+    
+    /**
+     * Method: selectBox
+     * Callback from the handlers.box set up when <box> selection is true
+     *     on.
+     *
+     * Parameters:
+     * position - {<OpenLayers.Bounds> || <OpenLayers.Pixel> }  
+     */
+    selectBox: function(position) {
+        if (position instanceof OpenLayers.Bounds) {
+            var minXY = this.map.getLonLatFromPixel(
+                new OpenLayers.Pixel(position.left, position.bottom)
+            );
+            var maxXY = this.map.getLonLatFromPixel(
+                new OpenLayers.Pixel(position.right, position.top)
+            );
+            var bounds = new OpenLayers.Bounds(
+                minXY.lon, minXY.lat, maxXY.lon, maxXY.lat
+            );
+            
+            // if multiple is false, first deselect currently selected features
+            if (!this.multipleSelect()) {
+                this.unselectAll();
+            }
+            
+            // because we're using a box, we consider we want multiple selection
+            var prevMultiple = this.multiple;
+            this.multiple = true;
+            for(var i=0, len = this.layer.features.length; i<len; ++i) {
+                var feature = this.layer.features[i];
+                if (this.geometryTypes == null || OpenLayers.Util.indexOf(
+                        this.geometryTypes, feature.geometry.CLASS_NAME) > -1) {
+                    if (bounds.toGeometry().intersects(feature.geometry)) {
+                        if (OpenLayers.Util.indexOf(this.layer.selectedFeatures, feature) == -1) {
+                            this.select(feature);
+                        }
+                    }
+                }
+            }
+            this.multiple = prevMultiple;
+        }
+    },
+
+    /** 
+     * Method: setMap
+     * Set the map property for the control. 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        this.handlers.feature.setMap(map);
+        if (this.box) {
+            this.handlers.box.setMap(map);
+        }
+        OpenLayers.Control.prototype.setMap.apply(this, arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Control.SelectFeature"
+});
+/* ======================================================================
     OpenLayers/Control/ZoomBox.js
    ====================================================================== */
 
@@ -13931,6 +30531,1260 @@
     CLASS_NAME: "OpenLayers.Control.ZoomBox"
 });
 /* ======================================================================
+    OpenLayers/Format/WKT.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WKT
+ * Class for reading and writing Well-Known Text.  Create a new instance
+ * with the <OpenLayers.Format.WKT> constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.WKT = OpenLayers.Class(OpenLayers.Format, {
+    
+    /**
+     * Constructor: OpenLayers.Format.WKT
+     * Create a new parser for WKT
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *           this instance
+     *
+     * Returns:
+     * {<OpenLayers.Format.WKT>} A new WKT parser.
+     */
+    initialize: function(options) {
+        this.regExes = {
+            'typeStr': /^\s*(\w+)\s*\(\s*(.*)\s*\)\s*$/,
+            'spaces': /\s+/,
+            'parenComma': /\)\s*,\s*\(/,
+            'doubleParenComma': /\)\s*\)\s*,\s*\(\s*\(/,  // can't use {2} here
+            'trimParens': /^\s*\(?(.*?)\)?\s*$/
+        };
+        OpenLayers.Format.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * Method: read
+     * Deserialize a WKT string and return a vector feature or an
+     * array of vector features.  Supports WKT for POINT, MULTIPOINT,
+     * LINESTRING, MULTILINESTRING, POLYGON, MULTIPOLYGON, and
+     * GEOMETRYCOLLECTION.
+     *
+     * Parameters:
+     * wkt - {String} A WKT string
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>|Array} A feature or array of features for
+     * GEOMETRYCOLLECTION WKT.
+     */
+    read: function(wkt) {
+        var features, type, str;
+        var matches = this.regExes.typeStr.exec(wkt);
+        if(matches) {
+            type = matches[1].toLowerCase();
+            str = matches[2];
+            if(this.parse[type]) {
+                features = this.parse[type].apply(this, [str]);
+            }
+            if (this.internalProjection && this.externalProjection) {
+                if (features && 
+                    features.CLASS_NAME == "OpenLayers.Feature.Vector") {
+                    features.geometry.transform(this.externalProjection,
+                                                this.internalProjection);
+                } else if (features &&
+                           type != "geometrycollection" &&
+                           typeof features == "object") {
+                    for (var i=0, len=features.length; i<len; i++) {
+                        var component = features[i];
+                        component.geometry.transform(this.externalProjection,
+                                                     this.internalProjection);
+                    }
+                }
+            }
+        }    
+        return features;
+    },
+
+    /**
+     * Method: write
+     * Serialize a feature or array of features into a WKT string.
+     *
+     * Parameters:
+     * features - {<OpenLayers.Feature.Vector>|Array} A feature or array of
+     *            features
+     *
+     * Returns:
+     * {String} The WKT string representation of the input geometries
+     */
+    write: function(features) {
+        var collection, geometry, type, data, isCollection;
+        if(features.constructor == Array) {
+            collection = features;
+            isCollection = true;
+        } else {
+            collection = [features];
+            isCollection = false;
+        }
+        var pieces = [];
+        if(isCollection) {
+            pieces.push('GEOMETRYCOLLECTION(');
+        }
+        for(var i=0, len=collection.length; i<len; ++i) {
+            if(isCollection && i>0) {
+                pieces.push(',');
+            }
+            geometry = collection[i].geometry;
+            type = geometry.CLASS_NAME.split('.')[2].toLowerCase();
+            if(!this.extract[type]) {
+                return null;
+            }
+            if (this.internalProjection && this.externalProjection) {
+                geometry = geometry.clone();
+                geometry.transform(this.internalProjection, 
+                                   this.externalProjection);
+            }                       
+            data = this.extract[type].apply(this, [geometry]);
+            pieces.push(type.toUpperCase() + '(' + data + ')');
+        }
+        if(isCollection) {
+            pieces.push(')');
+        }
+        return pieces.join('');
+    },
+    
+    /**
+     * Object with properties corresponding to the geometry types.
+     * Property values are functions that do the actual data extraction.
+     */
+    extract: {
+        /**
+         * Return a space delimited string of point coordinates.
+         * @param {<OpenLayers.Geometry.Point>} point
+         * @returns {String} A string of coordinates representing the point
+         */
+        'point': function(point) {
+            return point.x + ' ' + point.y;
+        },
+
+        /**
+         * Return a comma delimited string of point coordinates from a multipoint.
+         * @param {<OpenLayers.Geometry.MultiPoint>} multipoint
+         * @returns {String} A string of point coordinate strings representing
+         *                  the multipoint
+         */
+        'multipoint': function(multipoint) {
+            var array = [];
+            for(var i=0, len=multipoint.components.length; i<len; ++i) {
+                array.push(this.extract.point.apply(this, [multipoint.components[i]]));
+            }
+            return array.join(',');
+        },
+        
+        /**
+         * Return a comma delimited string of point coordinates from a line.
+         * @param {<OpenLayers.Geometry.LineString>} linestring
+         * @returns {String} A string of point coordinate strings representing
+         *                  the linestring
+         */
+        'linestring': function(linestring) {
+            var array = [];
+            for(var i=0, len=linestring.components.length; i<len; ++i) {
+                array.push(this.extract.point.apply(this, [linestring.components[i]]));
+            }
+            return array.join(',');
+        },
+
+        /**
+         * Return a comma delimited string of linestring strings from a multilinestring.
+         * @param {<OpenLayers.Geometry.MultiLineString>} multilinestring
+         * @returns {String} A string of of linestring strings representing
+         *                  the multilinestring
+         */
+        'multilinestring': function(multilinestring) {
+            var array = [];
+            for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+                array.push('(' +
+                           this.extract.linestring.apply(this, [multilinestring.components[i]]) +
+                           ')');
+            }
+            return array.join(',');
+        },
+        
+        /**
+         * Return a comma delimited string of linear ring arrays from a polygon.
+         * @param {<OpenLayers.Geometry.Polygon>} polygon
+         * @returns {String} An array of linear ring arrays representing the polygon
+         */
+        'polygon': function(polygon) {
+            var array = [];
+            for(var i=0, len=polygon.components.length; i<len; ++i) {
+                array.push('(' +
+                           this.extract.linestring.apply(this, [polygon.components[i]]) +
+                           ')');
+            }
+            return array.join(',');
+        },
+
+        /**
+         * Return an array of polygon arrays from a multipolygon.
+         * @param {<OpenLayers.Geometry.MultiPolygon>} multipolygon
+         * @returns {Array} An array of polygon arrays representing
+         *                  the multipolygon
+         */
+        'multipolygon': function(multipolygon) {
+            var array = [];
+            for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+                array.push('(' +
+                           this.extract.polygon.apply(this, [multipolygon.components[i]]) +
+                           ')');
+            }
+            return array.join(',');
+        }
+
+    },
+
+    /**
+     * Object with properties corresponding to the geometry types.
+     * Property values are functions that do the actual parsing.
+     */
+    parse: {
+        /**
+         * Return point feature given a point WKT fragment.
+         * @param {String} str A WKT fragment representing the point
+         * @returns {<OpenLayers.Feature.Vector>} A point feature
+         * @private
+         */
+        'point': function(str) {
+            var coords = OpenLayers.String.trim(str).split(this.regExes.spaces);
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.Point(coords[0], coords[1])
+            );
+        },
+
+        /**
+         * Return a multipoint feature given a multipoint WKT fragment.
+         * @param {String} A WKT fragment representing the multipoint
+         * @returns {<OpenLayers.Feature.Vector>} A multipoint feature
+         * @private
+         */
+        'multipoint': function(str) {
+            var points = OpenLayers.String.trim(str).split(',');
+            var components = [];
+            for(var i=0, len=points.length; i<len; ++i) {
+                components.push(this.parse.point.apply(this, [points[i]]).geometry);
+            }
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.MultiPoint(components)
+            );
+        },
+        
+        /**
+         * Return a linestring feature given a linestring WKT fragment.
+         * @param {String} A WKT fragment representing the linestring
+         * @returns {<OpenLayers.Feature.Vector>} A linestring feature
+         * @private
+         */
+        'linestring': function(str) {
+            var points = OpenLayers.String.trim(str).split(',');
+            var components = [];
+            for(var i=0, len=points.length; i<len; ++i) {
+                components.push(this.parse.point.apply(this, [points[i]]).geometry);
+            }
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.LineString(components)
+            );
+        },
+
+        /**
+         * Return a multilinestring feature given a multilinestring WKT fragment.
+         * @param {String} A WKT fragment representing the multilinestring
+         * @returns {<OpenLayers.Feature.Vector>} A multilinestring feature
+         * @private
+         */
+        'multilinestring': function(str) {
+            var line;
+            var lines = OpenLayers.String.trim(str).split(this.regExes.parenComma);
+            var components = [];
+            for(var i=0, len=lines.length; i<len; ++i) {
+                line = lines[i].replace(this.regExes.trimParens, '$1');
+                components.push(this.parse.linestring.apply(this, [line]).geometry);
+            }
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.MultiLineString(components)
+            );
+        },
+        
+        /**
+         * Return a polygon feature given a polygon WKT fragment.
+         * @param {String} A WKT fragment representing the polygon
+         * @returns {<OpenLayers.Feature.Vector>} A polygon feature
+         * @private
+         */
+        'polygon': function(str) {
+            var ring, linestring, linearring;
+            var rings = OpenLayers.String.trim(str).split(this.regExes.parenComma);
+            var components = [];
+            for(var i=0, len=rings.length; i<len; ++i) {
+                ring = rings[i].replace(this.regExes.trimParens, '$1');
+                linestring = this.parse.linestring.apply(this, [ring]).geometry;
+                linearring = new OpenLayers.Geometry.LinearRing(linestring.components);
+                components.push(linearring);
+            }
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.Polygon(components)
+            );
+        },
+
+        /**
+         * Return a multipolygon feature given a multipolygon WKT fragment.
+         * @param {String} A WKT fragment representing the multipolygon
+         * @returns {<OpenLayers.Feature.Vector>} A multipolygon feature
+         * @private
+         */
+        'multipolygon': function(str) {
+            var polygon;
+            var polygons = OpenLayers.String.trim(str).split(this.regExes.doubleParenComma);
+            var components = [];
+            for(var i=0, len=polygons.length; i<len; ++i) {
+                polygon = polygons[i].replace(this.regExes.trimParens, '$1');
+                components.push(this.parse.polygon.apply(this, [polygon]).geometry);
+            }
+            return new OpenLayers.Feature.Vector(
+                new OpenLayers.Geometry.MultiPolygon(components)
+            );
+        },
+
+        /**
+         * Return an array of features given a geometrycollection WKT fragment.
+         * @param {String} A WKT fragment representing the geometrycollection
+         * @returns {Array} An array of OpenLayers.Feature.Vector
+         * @private
+         */
+        'geometrycollection': function(str) {
+            // separate components of the collection with |
+            str = str.replace(/,\s*([A-Za-z])/g, '|$1');
+            var wktArray = OpenLayers.String.trim(str).split('|');
+            var components = [];
+            for(var i=0, len=wktArray.length; i<len; ++i) {
+                components.push(OpenLayers.Format.WKT.prototype.read.apply(this,[wktArray[i]]));
+            }
+            return components;
+        }
+
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WKT" 
+});     
+/* ======================================================================
+    OpenLayers/Layer/Boxes.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Layer/Markers.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Boxes
+ * Draw divs as 'boxes' on the layer. 
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.Boxes = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+    /**
+     * Constructor: OpenLayers.Layer.Boxes
+     *
+     * Parameters:
+     * name - {String} 
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function (name, options) {
+        OpenLayers.Layer.Markers.prototype.initialize.apply(this, arguments);
+    },
+    
+    /**
+     * Method: drawMarker 
+     * Calculate the pixel location for the marker, create it, and
+     *    add it to the layer's div
+     *
+     * Parameters: 
+     * marker - {<OpenLayers.Marker.Box>} 
+     */
+    drawMarker: function(marker) {
+        var bounds   = marker.bounds;
+        var topleft  = this.map.getLayerPxFromLonLat(
+                            new OpenLayers.LonLat(bounds.left,  bounds.top));
+        var botright = this.map.getLayerPxFromLonLat(
+                             new OpenLayers.LonLat(bounds.right, bounds.bottom));
+        if (botright == null || topleft == null) {
+            marker.display(false);
+        } else {
+            var sz = new OpenLayers.Size(
+                Math.max(1, botright.x - topleft.x),
+                Math.max(1, botright.y - topleft.y));
+            var markerDiv = marker.draw(topleft, sz);
+            if (!marker.drawn) {
+                this.div.appendChild(markerDiv);
+                marker.drawn = true;
+            }
+        }
+    },
+
+
+    /**
+     * APIMethod: removeMarker 
+     * 
+     * Parameters:
+     * marker - {<OpenLayers.Marker.Box>} 
+     */
+    removeMarker: function(marker) {
+        OpenLayers.Util.removeItem(this.markers, marker);
+        if ((marker.div != null) &&
+            (marker.div.parentNode == this.div) ) {
+            this.div.removeChild(marker.div);    
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Boxes"
+});
+/* ======================================================================
+    OpenLayers/Layer/GeoRSS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Markers.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.GeoRSS
+ * Add GeoRSS Point features to your map. 
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.Markers>
+ *  - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.GeoRSS = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+    /** 
+     * Property: location 
+     * {String} store url of text file 
+     */
+    location: null,
+
+    /** 
+     * Property: features 
+     * {Array(<OpenLayers.Feature>)} 
+     */
+    features: null,
+    
+    /**
+     * APIProperty: formatOptions
+     * {Object} Hash of options which should be passed to the format when it is
+     * created. Must be passed in the constructor.
+     */
+    formatOptions: null, 
+
+    /** 
+     * Property: selectedFeature 
+     * {<OpenLayers.Feature>} 
+     */
+    selectedFeature: null,
+
+    /** 
+     * APIProperty: icon 
+     * {<OpenLayers.Icon>}. This determines the Icon to be used on the map
+     * for this GeoRSS layer.
+     */
+    icon: null,
+
+    /**
+     * APIProperty: popupSize
+     * {<OpenLayers.Size>} This determines the size of GeoRSS popups. If 
+     * not provided, defaults to 250px by 120px. 
+     */
+    popupSize: null, 
+    
+    /** 
+     * APIProperty: useFeedTitle 
+     * {Boolean} Set layer.name to the first <title> element in the feed. Default is true. 
+     */
+    useFeedTitle: true,
+    
+    /**
+    * Constructor: OpenLayers.Layer.GeoRSS
+    * Create a GeoRSS Layer.
+    *
+    * Parameters:
+    * name - {String} 
+    * location - {String} 
+    * options - {Object}
+    */
+    initialize: function(name, location, options) {
+        OpenLayers.Layer.Markers.prototype.initialize.apply(this, [name, options]);
+        this.location = location;
+        this.features = [];
+    },
+
+    /**
+     * Method: destroy 
+     */
+    destroy: function() {
+        // Warning: Layer.Markers.destroy() must be called prior to calling
+        // clearFeatures() here, otherwise we leak memory. Indeed, if
+        // Layer.Markers.destroy() is called after clearFeatures(), it won't be
+        // able to remove the marker image elements from the layer's div since
+        // the markers will have been destroyed by clearFeatures().
+        OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+        this.clearFeatures();
+        this.features = null;
+    },
+
+    /**
+     * Method: loadRSS
+     * Start the load of the RSS data. Don't do this when we first add the layer,
+     * since we may not be visible at any point, and it would therefore be a waste.
+     */
+    loadRSS: function() {
+        if (!this.loaded) {
+            this.events.triggerEvent("loadstart");
+            OpenLayers.Request.GET({
+                url: this.location,
+                success: this.parseData,
+                scope: this
+            });
+            this.loaded = true;
+        }    
+    },    
+    
+    /**
+     * Method: moveTo
+     * If layer is visible and RSS has not been loaded, load RSS. 
+     * 
+     * Parameters:
+     * bounds - {Object} 
+     * zoomChanged - {Object} 
+     * minor - {Object} 
+     */
+    moveTo:function(bounds, zoomChanged, minor) {
+        OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+        if(this.visibility && !this.loaded){
+            this.loadRSS();
+        }
+    },
+        
+    /**
+     * Method: parseData
+     * Parse the data returned from the Events call.
+     *
+     * Parameters:
+     * ajaxRequest - {<OpenLayers.Request.XMLHttpRequest>} 
+     */
+    parseData: function(ajaxRequest) {
+        var doc = ajaxRequest.responseXML;
+        if (!doc || !doc.documentElement) {
+            doc = OpenLayers.Format.XML.prototype.read(ajaxRequest.responseText);
+        }
+        
+        if (this.useFeedTitle) {
+            var name = null;
+            try {
+                name = doc.getElementsByTagNameNS('*', 'title')[0].firstChild.nodeValue;
+            }
+            catch (e) {
+                name = doc.getElementsByTagName('title')[0].firstChild.nodeValue;
+            }
+            if (name) {
+                this.setName(name);
+            }    
+        }
+       
+        var options = {};
+        
+        OpenLayers.Util.extend(options, this.formatOptions);
+        
+        if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+            options.externalProjection = this.projection;
+            options.internalProjection = this.map.getProjectionObject();
+        }    
+        
+        var format = new OpenLayers.Format.GeoRSS(options);
+        var features = format.read(doc);
+        
+        for (var i=0, len=features.length; i<len; i++) {
+            var data = {};
+            var feature = features[i];
+            
+            // we don't support features with no geometry in the GeoRSS
+            // layer at this time. 
+            if (!feature.geometry) {
+                continue;
+            }    
+            
+            var title = feature.attributes.title ? 
+                         feature.attributes.title : "Untitled";
+            
+            var description = feature.attributes.description ? 
+                         feature.attributes.description : "No description.";
+            
+            var link = feature.attributes.link ? feature.attributes.link : "";
+
+            var location = feature.geometry.getBounds().getCenterLonLat();
+            
+            
+            data.icon = this.icon == null ? 
+                                     OpenLayers.Marker.defaultIcon() : 
+                                     this.icon.clone();
+            
+            data.popupSize = this.popupSize ? 
+                             this.popupSize.clone() :
+                             new OpenLayers.Size(250, 120);
+            
+            if (title || description) {
+                // we have supplemental data, store them.
+                data.title = title;
+                data.description = description;
+            
+                var contentHTML = '<div class="olLayerGeoRSSClose">[x]</div>'; 
+                contentHTML += '<div class="olLayerGeoRSSTitle">';
+                if (link) {
+                    contentHTML += '<a class="link" href="'+link+'" target="_blank">';
+                }
+                contentHTML += title;
+                if (link) {
+                    contentHTML += '</a>';
+                }
+                contentHTML += '</div>';
+                contentHTML += '<div style="" class="olLayerGeoRSSDescription">';
+                contentHTML += description;
+                contentHTML += '</div>';
+                data['popupContentHTML'] = contentHTML;                
+            }
+            var feature = new OpenLayers.Feature(this, location, data);
+            this.features.push(feature);
+            var marker = feature.createMarker();
+            marker.events.register('click', feature, this.markerClick);
+            this.addMarker(marker);
+        }
+        this.events.triggerEvent("loadend");
+    },
+    
+    /**
+     * Method: markerClick
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    markerClick: function(evt) {
+        var sameMarkerClicked = (this == this.layer.selectedFeature);
+        this.layer.selectedFeature = (!sameMarkerClicked) ? this : null;
+        for(var i=0, len=this.layer.map.popups.length; i<len; i++) {
+            this.layer.map.removePopup(this.layer.map.popups[i]);
+        }
+        if (!sameMarkerClicked) {
+            var popup = this.createPopup();
+            OpenLayers.Event.observe(popup.div, "click",
+                OpenLayers.Function.bind(function() { 
+                    for(var i=0, len=this.layer.map.popups.length; i<len; i++) { 
+                        this.layer.map.removePopup(this.layer.map.popups[i]); 
+                    }
+                }, this)
+            );
+            this.layer.map.addPopup(popup); 
+        }
+        OpenLayers.Event.stop(evt);
+    },
+
+    /**
+     * Method: clearFeatures
+     * Destroy all features in this layer.
+     */
+    clearFeatures: function() {
+        if (this.features != null) {
+            while(this.features.length > 0) {
+                var feature = this.features[0];
+                OpenLayers.Util.removeItem(this.features, feature);
+                feature.destroy();
+            }
+        }        
+    },
+    
+    CLASS_NAME: "OpenLayers.Layer.GeoRSS"
+});
+/* ======================================================================
+    OpenLayers/Layer/Google.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/SphericalMercator.js
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Google
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.SphericalMercator>
+ *  - <OpenLayers.Layer.EventPane>
+ *  - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.Google = OpenLayers.Class(
+    OpenLayers.Layer.EventPane, 
+    OpenLayers.Layer.FixedZoomLevels, {
+    
+    /** 
+     * Constant: MIN_ZOOM_LEVEL
+     * {Integer} 0 
+     */
+    MIN_ZOOM_LEVEL: 0,
+    
+    /** 
+     * Constant: MAX_ZOOM_LEVEL
+     * {Integer} 19
+     */
+    MAX_ZOOM_LEVEL: 19,
+
+    /** 
+     * Constant: RESOLUTIONS
+     * {Array(Float)} Hardcode these resolutions so that they are more closely
+     *                tied with the standard wms projection
+     */
+    RESOLUTIONS: [
+        1.40625, 
+        0.703125, 
+        0.3515625, 
+        0.17578125, 
+        0.087890625, 
+        0.0439453125,
+        0.02197265625, 
+        0.010986328125, 
+        0.0054931640625, 
+        0.00274658203125,
+        0.001373291015625, 
+        0.0006866455078125, 
+        0.00034332275390625,
+        0.000171661376953125, 
+        0.0000858306884765625, 
+        0.00004291534423828125,
+        0.00002145767211914062, 
+        0.00001072883605957031,
+        0.00000536441802978515, 
+        0.00000268220901489257
+    ],
+
+    /**
+     * APIProperty: type
+     * {GMapType}
+     */
+    type: null,
+
+    /**
+     * APIProperty: sphericalMercator
+     * {Boolean} Should the map act as a mercator-projected map? This will
+     *     cause all interactions with the map to be in the actual map 
+     *     projection, which allows support for vector drawing, overlaying 
+     *     other maps, etc. 
+     */
+    sphericalMercator: false, 
+    
+    /**
+     * Property: dragObject
+     * {GDraggableObject} Since 2.93, Google has exposed the ability to get
+     *     the maps GDraggableObject. We can now use this for smooth panning
+     */
+    dragObject: null, 
+
+    /** 
+     * Constructor: OpenLayers.Layer.Google
+     * 
+     * Parameters:
+     * name - {String} A name for the layer.
+     * options - {Object} An optional object whose properties will be set
+     *     on the layer.
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.EventPane.prototype.initialize.apply(this, arguments);
+        OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, 
+                                                                    arguments);
+        this.addContainerPxFunction();
+        if (this.sphericalMercator) {
+            OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+            this.initMercatorParameters();
+        }    
+    },
+    
+    /** 
+     * Method: loadMapObject
+     * Load the GMap and register appropriate event listeners. If we can't 
+     *     load GMap2, then display a warning message.
+     */
+    loadMapObject:function() {
+        
+        //has gmaps library has been loaded?
+        try {
+            // create GMap, hide nav controls
+            this.mapObject = new GMap2( this.div );
+            
+            //since v 2.93 getDragObject is now available.
+            if(typeof this.mapObject.getDragObject == "function") {
+                this.dragObject = this.mapObject.getDragObject();
+            } else {
+                this.dragPanMapObject = null;
+            }
+
+
+            // move the ToS and branding stuff up to the pane
+            // thanks a *mil* Erik for thinking of this
+            var poweredBy = this.div.lastChild;
+            this.div.removeChild(poweredBy);
+            this.pane.appendChild(poweredBy);
+            poweredBy.className = "olLayerGooglePoweredBy gmnoprint";
+            poweredBy.style.left = "";
+            poweredBy.style.bottom = "";
+
+            var termsOfUse = this.div.lastChild;
+            this.div.removeChild(termsOfUse);
+            this.pane.appendChild(termsOfUse);
+            termsOfUse.className = "olLayerGoogleCopyright";
+            termsOfUse.style.right = "";
+            termsOfUse.style.bottom = "";
+
+        } catch (e) {
+            OpenLayers.Console.error(e);
+        }
+               
+    },
+
+    /** 
+     * APIMethod: setMap
+     * Overridden from EventPane because if a map type has been specified, 
+     *     we need to attach a listener for the first moveend -- this is how 
+     *     we will know that the map has been centered. Only once the map has 
+     *     been centered is it safe to change the gmap object's map type. 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    setMap: function(map) {
+        OpenLayers.Layer.EventPane.prototype.setMap.apply(this, arguments);
+
+        if (this.type != null) {
+            this.map.events.register("moveend", this, this.setMapType);
+        }
+    },
+    
+    /** 
+     * Method: setMapType
+     * The map has been centered, and a map type was specified, so we 
+     *     set the map type on the gmap object, then unregister the listener
+     *     so that we dont keep doing this every time the map moves.
+     */
+    setMapType: function() {
+        if (this.mapObject.getCenter() != null) {
+            
+            // Support for custom map types.
+            if (OpenLayers.Util.indexOf(this.mapObject.getMapTypes(),
+                                        this.type) == -1) {
+                this.mapObject.addMapType(this.type);
+            }    
+
+            this.mapObject.setMapType(this.type);
+            this.map.events.unregister("moveend", this, this.setMapType);
+        }
+    },
+
+    /**
+     * APIMethod: onMapResize
+     * 
+     * Parameters:
+     * evt - {Event}
+     */
+    onMapResize: function() {
+        if(this.visibility) {
+            this.mapObject.checkResize();  
+        } else {
+            this.windowResized = true;
+        }
+    },
+    
+    /**
+     * Method: display
+     * Hide or show the layer
+     *
+     * Parameters:
+     * display - {Boolean}
+     */
+    display: function(display) {
+        OpenLayers.Layer.EventPane.prototype.display.apply(this, arguments);
+        if(this.div.style.display == "block" && this.windowResized) {
+            this.mapObject.checkResize();
+            this.windowResized = false;
+        }
+    },
+
+    /**
+     * APIMethod: getZoomForExtent
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     *  
+     * Returns:
+     * {Integer} Corresponding zoom level for a specified Bounds. 
+     *           If mapObject is not loaded or not centered, returns null
+     *
+    getZoomForExtent: function (bounds) {
+        var zoom = null;
+        if (this.mapObject != null) {
+            var moBounds = this.getMapObjectBoundsFromOLBounds(bounds);
+            var moZoom = this.getMapObjectZoomFromMapObjectBounds(moBounds);
+
+            //make sure zoom is within bounds    
+            var moZoom = Math.min(Math.max(moZoom, this.minZoomLevel), 
+                                 this.maxZoomLevel);
+
+            zoom = this.getOLZoomFromMapObjectZoom(moZoom);
+        }
+        return zoom;
+    },
+    
+    */
+    
+  //
+  // TRANSLATION: MapObject Bounds <-> OpenLayers.Bounds
+  //
+
+    /**
+     * APIMethod: getOLBoundsFromMapObjectBounds
+     * 
+     * Parameters:
+     * moBounds - {Object}
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>} An <OpenLayers.Bounds>, translated from the 
+     *                       passed-in MapObject Bounds.
+     *                       Returns null if null value is passed in.
+     */
+    getOLBoundsFromMapObjectBounds: function(moBounds) {
+        var olBounds = null;
+        if (moBounds != null) {
+            var sw = moBounds.getSouthWest();
+            var ne = moBounds.getNorthEast();
+            if (this.sphericalMercator) {
+                sw = this.forwardMercator(sw.lng(), sw.lat());
+                ne = this.forwardMercator(ne.lng(), ne.lat());
+            } else {
+                sw = new OpenLayers.LonLat(sw.lng(), sw.lat()); 
+                ne = new OpenLayers.LonLat(ne.lng(), ne.lat()); 
+            }    
+            olBounds = new OpenLayers.Bounds(sw.lon, 
+                                             sw.lat, 
+                                             ne.lon, 
+                                             ne.lat );
+        }
+        return olBounds;
+    },
+
+    /**
+     * APIMethod: getMapObjectBoundsFromOLBounds
+     * 
+     * Parameters:
+     * olBounds - {<OpenLayers.Bounds>}
+     * 
+     * Returns:
+     * {Object} A MapObject Bounds, translated from olBounds
+     *          Returns null if null value is passed in
+     */
+    getMapObjectBoundsFromOLBounds: function(olBounds) {
+        var moBounds = null;
+        if (olBounds != null) {
+            var sw = this.sphericalMercator ? 
+              this.inverseMercator(olBounds.bottom, olBounds.left) : 
+              new OpenLayers.LonLat(olBounds.bottom, olBounds.left);
+            var ne = this.sphericalMercator ? 
+              this.inverseMercator(olBounds.top, olBounds.right) : 
+              new OpenLayers.LonLat(olBounds.top, olBounds.right);
+            moBounds = new GLatLngBounds(new GLatLng(sw.lat, sw.lon),
+                                         new GLatLng(ne.lat, ne.lon));
+        }
+        return moBounds;
+    },
+
+    /** 
+     * Method: addContainerPxFunction
+     * Hack-on function because GMAPS does not give it to us
+     * 
+     * Parameters: 
+     * gLatLng - {GLatLng}
+     * 
+     * Returns:
+     * {GPoint} A GPoint specifying gLatLng translated into "Container" coords
+     */
+    addContainerPxFunction: function() {
+        if ( (typeof GMap2 != "undefined") && 
+             !GMap2.prototype.fromLatLngToContainerPixel) {
+          
+            GMap2.prototype.fromLatLngToContainerPixel = function(gLatLng) {
+          
+                // first we translate into "DivPixel"
+                var gPoint = this.fromLatLngToDivPixel(gLatLng);
+      
+                // locate the sliding "Div" div
+                var div = this.getContainer().firstChild.firstChild;
+  
+                // adjust by the offset of "Div" and voila!
+                gPoint.x += div.offsetLeft;
+                gPoint.y += div.offsetTop;
+    
+                return gPoint;
+            };
+        }
+    },
+
+    /** 
+     * APIMethod: getWarningHTML
+     * 
+     * Returns: 
+     * {String} String with information on why layer is broken, how to get
+     *          it working.
+     */
+    getWarningHTML:function() {
+        return OpenLayers.i18n("googleWarning");
+    },
+
+
+    /************************************
+     *                                  *
+     *   MapObject Interface Controls   *
+     *                                  *
+     ************************************/
+
+
+  // Get&Set Center, Zoom
+
+    /** 
+     * APIMethod: setMapObjectCenter
+     * Set the mapObject to the specified center and zoom
+     * 
+     * Parameters:
+     * center - {Object} MapObject LonLat format
+     * zoom - {int} MapObject zoom format
+     */
+    setMapObjectCenter: function(center, zoom) {
+        this.mapObject.setCenter(center, zoom); 
+    },
+   
+    /**
+     * APIMethod: dragPanMapObject
+     * 
+     * Parameters:
+     * dX - {Integer}
+     * dY - {Integer}
+     */
+    dragPanMapObject: function(dX, dY) {
+        this.dragObject.moveBy(new GSize(-dX, dY));
+    },
+
+    /**
+     * APIMethod: getMapObjectCenter
+     * 
+     * Returns: 
+     * {Object} The mapObject's current center in Map Object format
+     */
+    getMapObjectCenter: function() {
+        return this.mapObject.getCenter();
+    },
+
+    /** 
+     * APIMethod: getMapObjectZoom
+     * 
+     * Returns:
+     * {Integer} The mapObject's current zoom, in Map Object format
+     */
+    getMapObjectZoom: function() {
+        return this.mapObject.getZoom();
+    },
+
+
+  // LonLat - Pixel Translation
+  
+    /**
+     * APIMethod: getMapObjectLonLatFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Object} MapObject LonLat translated from MapObject Pixel
+     */
+    getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+        return this.mapObject.fromContainerPixelToLatLng(moPixel);
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Object} MapObject Pixel transtlated from MapObject LonLat
+     */
+    getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+        return this.mapObject.fromLatLngToContainerPixel(moLonLat);
+    },
+
+  
+  // Bounds
+  
+    /** 
+     * APIMethod: getMapObjectZoomFromMapObjectBounds
+     * 
+     * Parameters:
+     * moBounds - {Object} MapObject Bounds format
+     * 
+     * Returns:
+     * {Object} MapObject Zoom for specified MapObject Bounds
+     */
+    getMapObjectZoomFromMapObjectBounds: function(moBounds) {
+        return this.mapObject.getBoundsZoomLevel(moBounds);
+    },
+
+    /************************************
+     *                                  *
+     *       MapObject Primitives       *
+     *                                  *
+     ************************************/
+
+
+  // LonLat
+    
+    /**
+     * APIMethod: getLongitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Longitude of the given MapObject LonLat
+     */
+    getLongitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+          this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lon :
+          moLonLat.lng();  
+    },
+
+    /**
+     * APIMethod: getLatitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Latitude of the given MapObject LonLat
+     */
+    getLatitudeFromMapObjectLonLat: function(moLonLat) {
+        var lat = this.sphericalMercator ? 
+          this.forwardMercator(moLonLat.lng(), moLonLat.lat()).lat :
+          moLonLat.lat(); 
+        return lat;  
+    },
+    
+    /**
+     * APIMethod: getMapObjectLonLatFromLonLat
+     * 
+     * Parameters:
+     * lon - {Float}
+     * lat - {Float}
+     * 
+     * Returns:
+     * {Object} MapObject LonLat built from lon and lat params
+     */
+    getMapObjectLonLatFromLonLat: function(lon, lat) {
+        var gLatLng;
+        if(this.sphericalMercator) {
+            var lonlat = this.inverseMercator(lon, lat);
+            gLatLng = new GLatLng(lonlat.lat, lonlat.lon);
+        } else {
+            gLatLng = new GLatLng(lat, lon);
+        }
+        return gLatLng;
+    },
+
+  // Pixel
+    
+    /**
+     * APIMethod: getXFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} X value of the MapObject Pixel
+     */
+    getXFromMapObjectPixel: function(moPixel) {
+        return moPixel.x;
+    },
+
+    /**
+     * APIMethod: getYFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} Y value of the MapObject Pixel
+     */
+    getYFromMapObjectPixel: function(moPixel) {
+        return moPixel.y;
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromXY
+     * 
+     * Parameters:
+     * x - {Integer}
+     * y - {Integer}
+     * 
+     * Returns:
+     * {Object} MapObject Pixel from x and y parameters
+     */
+    getMapObjectPixelFromXY: function(x, y) {
+        return new GPoint(x, y);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Google"
+});
+/* ======================================================================
     OpenLayers/Layer/Grid.js
    ====================================================================== */
 
@@ -14037,9 +31891,9 @@
      */
     clearGrid:function() {
         if (this.grid) {
-            for(var iRow=0; iRow < this.grid.length; iRow++) {
+            for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
                 var row = this.grid[iRow];
-                for(var iCol=0; iCol < row.length; iCol++) {
+                for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
                     var tile = row[iCol];
                     this.removeTileMonitoringHooks(tile);
                     tile.destroy();
@@ -14289,7 +32143,7 @@
         var minCols = Math.ceil(viewSize.w/this.tileSize.w) +
                       Math.max(1, 2 * this.buffer);
         
-        var extent = this.map.getMaxExtent();
+        var extent = this.maxExtent;
         var resolution = this.map.getResolution();
         
         var tileLayout = this.calculateGridLayout(bounds, extent, resolution);
@@ -14430,7 +32284,7 @@
         } 
         
         // now we go through and draw the tiles in forward order
-        for(var i=0; i < tileQueue.length; i++) {
+        for(var i=0, len=tileQueue.length; i<len; i++) {
             var tile = tileQueue[i];
             tile.draw();
             //mark tile as unqueued for the next time (since tiles are reused)
@@ -14549,7 +32403,7 @@
 
         var row = (prepend) ? grid.pop() : grid.shift();
 
-        for (var i=0; i < modelRow.length; i++) {
+        for (var i=0, len=modelRow.length; i<len; i++) {
             var modelTile = modelRow[i];
             var bounds = modelTile.bounds.clone();
             var position = modelTile.position.clone();
@@ -14579,7 +32433,7 @@
         var resolution = this.map.getResolution();
         var deltaLon = resolution * deltaX;
 
-        for (var i=0; i<this.grid.length; i++) {
+        for (var i=0, len=this.grid.length; i<len; i++) {
             var row = this.grid[i];
             var modelTileIndex = (prepend) ? 0 : (row.length - 1);
             var modelTile = row[modelTileIndex];
@@ -14655,7 +32509,7 @@
      * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
      */
     getTileBounds: function(viewPortPx) {
-        var maxExtent = this.map.getMaxExtent();
+        var maxExtent = this.maxExtent;
         var resolution = this.getResolution();
         var tileMapWidth = resolution * this.tileSize.w;
         var tileMapHeight = resolution * this.tileSize.h;
@@ -14676,6 +32530,2379 @@
     CLASS_NAME: "OpenLayers.Layer.Grid"
 });
 /* ======================================================================
+    OpenLayers/Layer/MultiMap.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.MultiMap
+ * Note that MultiMap does not fully support the sphericalMercator
+ * option. See Ticket #953 for more details.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.EventPane>
+ *  - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.MultiMap = OpenLayers.Class(
+  OpenLayers.Layer.EventPane, OpenLayers.Layer.FixedZoomLevels, {
+    
+    /** 
+     * Constant: MIN_ZOOM_LEVEL
+     * {Integer} 1 
+     */
+    MIN_ZOOM_LEVEL: 1,
+    
+    /** 
+     * Constant: MAX_ZOOM_LEVEL
+     * {Integer} 17
+     */
+    MAX_ZOOM_LEVEL: 17,
+
+    /** 
+     * Constant: RESOLUTIONS
+     * {Array(Float)} Hardcode these resolutions so that they are more closely
+     *                tied with the standard wms projection
+     */
+    RESOLUTIONS: [
+        9, 
+        1.40625,
+        0.703125,
+        0.3515625,
+        0.17578125,
+        0.087890625,
+        0.0439453125,
+        0.02197265625,
+        0.010986328125,
+        0.0054931640625,
+        0.00274658203125,
+        0.001373291015625,
+        0.0006866455078125,
+        0.00034332275390625,
+        0.000171661376953125,
+        0.0000858306884765625,
+        0.00004291534423828125
+    ],
+
+    /**
+     * APIProperty: type
+     * {?}
+     */
+    type: null,
+
+    /** 
+     * Constructor: OpenLayers.Layer.MultiMap
+     * 
+     * Parameters:
+     * name - {String}
+     * options - {Object}
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.EventPane.prototype.initialize.apply(this, arguments);
+        OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, 
+                                                                    arguments);
+        if (this.sphericalMercator) {
+            OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+            this.initMercatorParameters();
+            this.RESOLUTIONS.unshift(10); 
+        }    
+    },
+    
+    /**
+     * Method: loadMapObject
+     */
+    loadMapObject:function() {
+        try { //crash proofing
+            this.mapObject = new MultimapViewer(this.div);
+        } catch (e) { }
+    },
+
+    /** 
+     * APIMethod: getWarningHTML
+     * 
+     * Returns: 
+     * {String} String with information on why layer is broken, how to get
+     *          it working.
+     */
+    getWarningHTML:function() {
+        return OpenLayers.i18n(
+            "getLayerWarning", {'layerType':"MM", 'layerLib':"MultiMap"}
+        );
+    },
+
+
+
+    /************************************
+     *                                  *
+     *   MapObject Interface Controls   *
+     *                                  *
+     ************************************/
+
+
+  // Get&Set Center, Zoom
+
+    /** 
+     * APIMethod: setMapObjectCenter
+     * Set the mapObject to the specified center and zoom
+     * 
+     * Parameters:
+     * center - {Object} MapObject LonLat format
+     * zoom - {int} MapObject zoom format
+     */
+    setMapObjectCenter: function(center, zoom) {
+        this.mapObject.goToPosition(center, zoom); 
+    },
+   
+    /**
+     * APIMethod: getMapObjectCenter
+     * 
+     * Returns: 
+     * {Object} The mapObject's current center in Map Object format
+     */
+    getMapObjectCenter: function() {
+        return this.mapObject.getCurrentPosition();
+    },
+
+    /** 
+     * APIMethod: getMapObjectZoom
+     * 
+     * Returns:
+     * {Integer} The mapObject's current zoom, in Map Object format
+     */
+    getMapObjectZoom: function() {
+        return this.mapObject.getZoomFactor();
+    },
+
+
+  // LonLat - Pixel Translation
+  
+    /**
+     * APIMethod: getMapObjectLonLatFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Object} MapObject LonLat translated from MapObject Pixel
+     */
+    getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+        moPixel.x = moPixel.x - (this.map.getSize().w/2);
+        moPixel.y = moPixel.y - (this.map.getSize().h/2);
+        return this.mapObject.getMapPositionAt(moPixel);
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Object} MapObject Pixel transtlated from MapObject LonLat
+     */
+    getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+        return this.mapObject.geoPosToContainerPixels(moLonLat);
+    },
+
+
+    /************************************
+     *                                  *
+     *       MapObject Primitives       *
+     *                                  *
+     ************************************/
+
+
+  // LonLat
+    
+    /**
+     * APIMethod: getLongitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Longitude of the given MapObject LonLat
+     */
+    getLongitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.lon, moLonLat.lat).lon :
+            moLonLat.lon;
+    },
+
+    /**
+     * APIMethod: getLatitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Latitude of the given MapObject LonLat
+     */
+    getLatitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.lon, moLonLat.lat).lat :
+            moLonLat.lat;
+    },
+
+    /**
+     * APIMethod: getMapObjectLonLatFromLonLat
+     * 
+     * Parameters:
+     * lon - {Float}
+     * lat - {Float}
+     * 
+     * Returns:
+     * {Object} MapObject LonLat built from lon and lat params
+     */
+    getMapObjectLonLatFromLonLat: function(lon, lat) {
+        var mmLatLon;
+        if(this.sphericalMercator) {
+            var lonlat = this.inverseMercator(lon, lat);
+            mmLatLon = new MMLatLon(lonlat.lat, lonlat.lon);
+        } else {
+            mmLatLon = new MMLatLon(lat, lon);
+        }
+        return mmLatLon;
+    },
+
+  // Pixel
+    
+    /**
+     * APIMethod: getXFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} X value of the MapObject Pixel
+     */
+    getXFromMapObjectPixel: function(moPixel) {
+        return moPixel.x;
+    },
+
+    /**
+     * APIMethod: getYFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} Y value of the MapObject Pixel
+     */
+    getYFromMapObjectPixel: function(moPixel) {
+        return moPixel.y;
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromXY
+     * 
+     * Parameters:
+     * x - {Integer}
+     * y - {Integer}
+     * 
+     * Returns:
+     * {Object} MapObject Pixel from x and y parameters
+     */
+    getMapObjectPixelFromXY: function(x, y) {
+        return new MMPoint(x, y);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.MultiMap"
+});
+/* ======================================================================
+    OpenLayers/Layer/Text.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Markers.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Text
+ * This layer creates markers given data in a text file.  The <location>
+ *     property of the layer (specified as a property of the options argument
+ *     in the <OpenLayers.Layer.Text> constructor) points to a tab delimited
+ *     file with data used to create markers.
+ *
+ * The first row of the data file should be a header line with the column names
+ *     of the data. Each column should be delimited by a tab space. The
+ *     possible columns are:
+ *      - *point* lat,lon of the point where a marker is to be placed
+ *      - *lat*  Latitude of the point where a marker is to be placed
+ *      - *lon*  Longitude of the point where a marker is to be placed
+ *      - *icon* or *image* URL of marker icon to use.
+ *      - *iconSize* Size of Icon to use.
+ *      - *iconOffset* Where the top-left corner of the icon is to be placed
+ *            relative to the latitude and longitude of the point.
+ *      - *title* The text of the 'title' is placed inside an 'h2' marker
+ *            inside a popup, which opens when the marker is clicked.
+ *      - *description* The text of the 'description' is placed below the h2
+ *            in the popup. this can be plain text or HTML.
+ *
+ * Example text file:
+ * (code)
+ * lat	lon	title	description	iconSize	iconOffset	icon
+ * 10	20	title	description	21,25		-10,-25		http://www.openlayers.org/dev/img/marker.png
+ * (end)
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.Text = OpenLayers.Class(OpenLayers.Layer.Markers, {
+
+    /**
+     * APIProperty: location 
+     * {String} URL of text file.  Must be specified in the "options" argument
+     *   of the constructor. Can not be changed once passed in. 
+     */
+    location:null,
+
+    /** 
+     * Property: features
+     * {Array(<OpenLayers.Feature>)} 
+     */
+    features: null,
+    
+    /**
+     * APIProperty: formatOptions
+     * {Object} Hash of options which should be passed to the format when it is
+     * created. Must be passed in the constructor.
+     */
+    formatOptions: null, 
+
+    /** 
+     * Property: selectedFeature
+     * {<OpenLayers.Feature>}
+     */
+    selectedFeature: null,
+
+    /**
+     * Constructor: OpenLayers.Layer.Text
+     * Create a text layer.
+     * 
+     * Parameters:
+     * name - {String} 
+     * options - {Object} Object with properties to be set on the layer.
+     *     Must include <location> property.
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.Markers.prototype.initialize.apply(this, arguments);
+        this.features = new Array();
+    },
+
+    /**
+     * APIMethod: destroy 
+     */
+    destroy: function() {
+        // Warning: Layer.Markers.destroy() must be called prior to calling
+        // clearFeatures() here, otherwise we leak memory. Indeed, if
+        // Layer.Markers.destroy() is called after clearFeatures(), it won't be
+        // able to remove the marker image elements from the layer's div since
+        // the markers will have been destroyed by clearFeatures().
+        OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+        this.clearFeatures();
+        this.features = null;
+    },
+    
+    /**
+     * Method: loadText
+     * Start the load of the Text data. Don't do this when we first add the layer,
+     * since we may not be visible at any point, and it would therefore be a waste.
+     */
+    loadText: function() {
+        if (!this.loaded) {
+            if (this.location != null) {
+
+                var onFail = function(e) {
+                    this.events.triggerEvent("loadend");
+                };
+
+                this.events.triggerEvent("loadstart");
+                OpenLayers.Request.GET({
+                    url: this.location,
+                    success: this.parseData,
+                    failure: onFail,
+                    scope: this
+                });
+                this.loaded = true;
+            }
+        }    
+    },    
+    
+    /**
+     * Method: moveTo
+     * If layer is visible and Text has not been loaded, load Text. 
+     * 
+     * Parameters:
+     * bounds - {Object} 
+     * zoomChanged - {Object} 
+     * minor - {Object} 
+     */
+    moveTo:function(bounds, zoomChanged, minor) {
+        OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+        if(this.visibility && !this.loaded){
+            this.loadText();
+        }
+    },
+    
+    /**
+     * Method: parseData
+     *
+     * Parameters:
+     * ajaxRequest - {<OpenLayers.Request.XMLHttpRequest>} 
+     */
+    parseData: function(ajaxRequest) {
+        var text = ajaxRequest.responseText;
+        
+        var options = {};
+        
+        OpenLayers.Util.extend(options, this.formatOptions);
+        
+        if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+            options.externalProjection = this.projection;
+            options.internalProjection = this.map.getProjectionObject();
+        }    
+        
+        var parser = new OpenLayers.Format.Text(options);
+        features = parser.read(text);
+        for (var i=0, len=features.length; i<len; i++) {
+            var data = {};
+            var feature = features[i];
+            var location;
+            var iconSize, iconOffset;
+            
+            location = new OpenLayers.LonLat(feature.geometry.x, 
+                                             feature.geometry.y);
+            
+            if (feature.style.graphicWidth 
+                && feature.style.graphicHeight) {
+                iconSize = new OpenLayers.Size(
+                    feature.style.graphicWidth,
+                    feature.style.graphicHeight);
+            }        
+            
+            // FIXME: At the moment, we only use this if we have an 
+            // externalGraphic, because icon has no setOffset API Method.
+            /**
+             * FIXME FIRST!!
+             * The Text format does all sorts of parseFloating
+             * The result of a parseFloat for a bogus string is NaN.  That
+             * means the three possible values here are undefined, NaN, or a
+             * number.  The previous check was an identity check for null.  This
+             * means it was failing for all undefined or NaN.  A slightly better
+             * check is for undefined.  An even better check is to see if the
+             * value is a number (see #1441).
+             */
+            if (feature.style.graphicXOffset !== undefined
+                && feature.style.graphicYOffset !== undefined) {
+                iconOffset = new OpenLayers.Pixel(
+                    feature.style.graphicXOffset, 
+                    feature.style.graphicYOffset);
+            }
+            
+            if (feature.style.externalGraphic != null) {
+                data.icon = new OpenLayers.Icon(feature.style.externalGraphic, 
+                                                iconSize, 
+                                                iconOffset);
+            } else {
+                data.icon = OpenLayers.Marker.defaultIcon();
+
+                //allows for the case where the image url is not 
+                // specified but the size is. use a default icon
+                // but change the size
+                if (iconSize != null) {
+                    data.icon.setSize(iconSize);
+                }
+            }
+            
+            if ((feature.attributes.title != null) 
+                && (feature.attributes.description != null)) {
+                data['popupContentHTML'] = 
+                    '<h2>'+feature.attributes.title+'</h2>' + 
+                    '<p>'+feature.attributes.description+'</p>';
+            }
+            
+            data['overflow'] = feature.attributes.overflow || "auto"; 
+            
+            var markerFeature = new OpenLayers.Feature(this, location, data);
+            this.features.push(markerFeature);
+            var marker = markerFeature.createMarker();
+            if ((feature.attributes.title != null) 
+                && (feature.attributes.description != null)) {
+              marker.events.register('click', markerFeature, this.markerClick);
+            }
+            this.addMarker(marker);
+        }
+        this.events.triggerEvent("loadend");
+    },
+    
+    /**
+     * Property: markerClick
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    markerClick: function(evt) {
+        var sameMarkerClicked = (this == this.layer.selectedFeature);
+        this.layer.selectedFeature = (!sameMarkerClicked) ? this : null;
+        for(var i=0, len=this.layer.map.popups.length; i<len; i++) {
+            this.layer.map.removePopup(this.layer.map.popups[i]);
+        }
+        if (!sameMarkerClicked) {
+            this.layer.map.addPopup(this.createPopup()); 
+        }
+        OpenLayers.Event.stop(evt);
+    },
+
+    /**
+     * Method: clearFeatures
+     */
+    clearFeatures: function() {
+        if (this.features != null) {
+            while(this.features.length > 0) {
+                var feature = this.features[0];
+                OpenLayers.Util.removeItem(this.features, feature);
+                feature.destroy();
+            }
+        }        
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Text"
+});
+/* ======================================================================
+    OpenLayers/Layer/VirtualEarth.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.VirtualEarth
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.EventPane>
+ *  - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.VirtualEarth = OpenLayers.Class(
+    OpenLayers.Layer.EventPane,
+    OpenLayers.Layer.FixedZoomLevels, {
+    
+    /** 
+     * Constant: MIN_ZOOM_LEVEL
+     * {Integer} 1 
+     */
+    MIN_ZOOM_LEVEL: 1,
+    
+    /** 
+     * Constant: MAX_ZOOM_LEVEL
+     * {Integer} 17
+     */
+    MAX_ZOOM_LEVEL: 17,
+
+    /** 
+     * Constant: RESOLUTIONS
+     * {Array(Float)} Hardcode these resolutions so that they are more closely
+     *                tied with the standard wms projection
+     */
+    RESOLUTIONS: [
+        1.40625, 
+        0.703125, 
+        0.3515625, 
+        0.17578125, 
+        0.087890625, 
+        0.0439453125,
+        0.02197265625, 
+        0.010986328125, 
+        0.0054931640625, 
+        0.00274658203125,
+        0.001373291015625, 
+        0.0006866455078125, 
+        0.00034332275390625, 
+        0.000171661376953125, 
+        0.0000858306884765625, 
+        0.00004291534423828125
+    ],
+
+    /**
+     * APIProperty: type
+     * {VEMapType}
+     */
+    type: null,
+
+    /**
+     * APIProperty: sphericalMercator
+     * {Boolean} Should the map act as a mercator-projected map? This will
+     *     cause all interactions with the map to be in the actual map
+     *     projection, which allows support for vector drawing, overlaying
+     *     other maps, etc. 
+     */
+    sphericalMercator: false, 
+
+    /** 
+     * Constructor: OpenLayers.Layer.VirtualEarth
+     * 
+     * Parameters:
+     * name - {String}
+     * options - {Object}
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.EventPane.prototype.initialize.apply(this, arguments);
+        OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, 
+                                                                    arguments);
+        if(this.sphericalMercator) {
+            OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+            this.initMercatorParameters();
+        }
+    },
+    
+    /**
+     * Method: loadMapObject
+     */
+    loadMapObject:function() {
+
+        // create div and set to same size as map
+        var veDiv = OpenLayers.Util.createDiv(this.name);
+        var sz = this.map.getSize();
+        veDiv.style.width = sz.w + "px";
+        veDiv.style.height = sz.h + "px";
+        this.div.appendChild(veDiv);
+
+        try { // crash prevention
+            this.mapObject = new VEMap(this.name);
+        } catch (e) { }
+
+        if (this.mapObject != null) {
+            try { // this is to catch a Mozilla bug without falling apart
+
+                // The fourth argument is whether the map is 'fixed' -- not 
+                // draggable. See: 
+                // http://blogs.msdn.com/virtualearth/archive/2007/09/28/locking-a-virtual-earth-map.aspx
+                //
+                this.mapObject.LoadMap(null, null, this.type, true);
+                this.mapObject.AttachEvent("onmousedown", function() {return true; });
+
+            } catch (e) { }
+            this.mapObject.HideDashboard();
+        }
+
+        //can we do smooth panning? this is an unpublished method, so we need 
+        // to be careful
+        if ( !this.mapObject ||
+             !this.mapObject.vemapcontrol ||
+             !this.mapObject.vemapcontrol.PanMap ||
+             (typeof this.mapObject.vemapcontrol.PanMap != "function")) {
+
+            this.dragPanMapObject = null;
+        }
+
+    },
+
+    /** 
+     * APIMethod: getWarningHTML
+     * 
+     * Returns: 
+     * {String} String with information on why layer is broken, how to get
+     *          it working.
+     */
+    getWarningHTML:function() {
+        return OpenLayers.i18n(
+            "getLayerWarning", {'layerType':'VE', 'layerLib':'VirtualEarth'}
+        );
+    },
+
+
+
+    /************************************
+     *                                  *
+     *   MapObject Interface Controls   *
+     *                                  *
+     ************************************/
+
+
+  // Get&Set Center, Zoom
+
+    /** 
+     * APIMethod: setMapObjectCenter
+     * Set the mapObject to the specified center and zoom
+     * 
+     * Parameters:
+     * center - {Object} MapObject LonLat format
+     * zoom - {int} MapObject zoom format
+     */
+    setMapObjectCenter: function(center, zoom) {
+        this.mapObject.SetCenterAndZoom(center, zoom); 
+    },
+   
+    /**
+     * APIMethod: getMapObjectCenter
+     * 
+     * Returns: 
+     * {Object} The mapObject's current center in Map Object format
+     */
+    getMapObjectCenter: function() {
+        return this.mapObject.GetCenter();
+    },
+
+    /**
+     * APIMethod: dragPanMapObject
+     * 
+     * Parameters:
+     * dX - {Integer}
+     * dY - {Integer}
+     */
+    dragPanMapObject: function(dX, dY) {
+        this.mapObject.vemapcontrol.PanMap(dX, -dY);
+    },
+
+    /** 
+     * APIMethod: getMapObjectZoom
+     * 
+     * Returns:
+     * {Integer} The mapObject's current zoom, in Map Object format
+     */
+    getMapObjectZoom: function() {
+        return this.mapObject.GetZoomLevel();
+    },
+
+
+  // LonLat - Pixel Translation
+  
+    /**
+     * APIMethod: getMapObjectLonLatFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Object} MapObject LonLat translated from MapObject Pixel
+     */
+    getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+        //the conditional here is to test if we are running the v6 of VE
+        return (typeof VEPixel != 'undefined') 
+            ? this.mapObject.PixelToLatLong(moPixel)
+            : this.mapObject.PixelToLatLong(moPixel.x, moPixel.y);
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Object} MapObject Pixel transtlated from MapObject LonLat
+     */
+    getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+        return this.mapObject.LatLongToPixel(moLonLat);
+    },
+
+
+    /************************************
+     *                                  *
+     *       MapObject Primitives       *
+     *                                  *
+     ************************************/
+
+
+  // LonLat
+    
+    /**
+     * APIMethod: getLongitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Longitude of the given MapObject LonLat
+     */
+    getLongitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.Longitude, moLonLat.Latitude).lon :
+            moLonLat.Longitude;
+    },
+
+    /**
+     * APIMethod: getLatitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Latitude of the given MapObject LonLat
+     */
+    getLatitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.Longitude, moLonLat.Latitude).lat :
+            moLonLat.Latitude;
+    },
+
+    /**
+     * APIMethod: getMapObjectLonLatFromLonLat
+     * 
+     * Parameters:
+     * lon - {Float}
+     * lat - {Float}
+     * 
+     * Returns:
+     * {Object} MapObject LonLat built from lon and lat params
+     */
+    getMapObjectLonLatFromLonLat: function(lon, lat) {
+        var veLatLong;
+        if(this.sphericalMercator) {
+            var lonlat = this.inverseMercator(lon, lat);
+            veLatLong = new VELatLong(lonlat.lat, lonlat.lon);
+        } else {
+            veLatLong = new VELatLong(lat, lon);
+        }
+        return veLatLong;
+    },
+
+  // Pixel
+    
+    /**
+     * APIMethod: getXFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} X value of the MapObject Pixel
+     */
+    getXFromMapObjectPixel: function(moPixel) {
+        return moPixel.x;
+    },
+
+    /**
+     * APIMethod: getYFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} Y value of the MapObject Pixel
+     */
+    getYFromMapObjectPixel: function(moPixel) {
+        return moPixel.y;
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromXY
+     * 
+     * Parameters:
+     * x - {Integer}
+     * y - {Integer}
+     * 
+     * Returns:
+     * {Object} MapObject Pixel from x and y parameters
+     */
+    getMapObjectPixelFromXY: function(x, y) {
+        //the conditional here is to test if we are running the v6 of VE
+        return (typeof VEPixel != 'undefined') ? new VEPixel(x, y)
+                         : new Msn.VE.Pixel(x, y);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.VirtualEarth"
+});
+/* ======================================================================
+    OpenLayers/Layer/Yahoo.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/EventPane.js
+ * @requires OpenLayers/Layer/FixedZoomLevels.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Yahoo
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.EventPane>
+ *  - <OpenLayers.Layer.FixedZoomLevels>
+ */
+OpenLayers.Layer.Yahoo = OpenLayers.Class(
+  OpenLayers.Layer.EventPane, OpenLayers.Layer.FixedZoomLevels, {
+    
+    /** 
+     * Constant: MIN_ZOOM_LEVEL
+     * {Integer} 0 
+     */
+    MIN_ZOOM_LEVEL: 0,
+    
+    /** 
+     * Constant: MAX_ZOOM_LEVEL
+     * {Integer} 15
+     */
+    MAX_ZOOM_LEVEL: 15,
+
+    /** 
+     * Constant: RESOLUTIONS
+     * {Array(Float)} Hardcode these resolutions so that they are more closely
+     *                tied with the standard wms projection
+     */
+    RESOLUTIONS: [
+        1.40625, 
+        0.703125, 
+        0.3515625, 
+        0.17578125, 
+        0.087890625, 
+        0.0439453125,
+        0.02197265625, 
+        0.010986328125, 
+        0.0054931640625, 
+        0.00274658203125, 
+        0.001373291015625, 
+        0.0006866455078125, 
+        0.00034332275390625, 
+        0.000171661376953125, 
+        0.0000858306884765625, 
+        0.00004291534423828125
+    ],
+
+    /**
+     * APIProperty: type
+     * {YahooMapType}
+     */
+    type: null,
+    
+    /**
+     * APIProperty: sphericalMercator
+     * {Boolean} Should the map act as a mercator-projected map? This will
+     * cause all interactions with the map to be in the actual map projection,
+     * which allows support for vector drawing, overlaying other maps, etc. 
+     */
+    sphericalMercator: false, 
+
+    /** 
+     * Constructor: OpenLayers.Layer.Yahoo
+     * 
+     * Parameters:
+     * name - {String}
+     * options - {Object}
+     */
+    initialize: function(name, options) {
+        OpenLayers.Layer.EventPane.prototype.initialize.apply(this, arguments);
+        OpenLayers.Layer.FixedZoomLevels.prototype.initialize.apply(this, 
+                                                                    arguments);
+        if(this.sphericalMercator) {
+            OpenLayers.Util.extend(this, OpenLayers.Layer.SphericalMercator);
+            this.initMercatorParameters();
+        }
+    },
+    
+    /**
+     * Method: loadMapObject
+     */
+    loadMapObject:function() {
+        try { //do not crash! 
+            var size = this.getMapObjectSizeFromOLSize(this.map.getSize());
+            this.mapObject = new YMap(this.div, this.type, size);
+            this.mapObject.disableKeyControls();
+            this.mapObject.disableDragMap();
+
+            //can we do smooth panning? (moveByXY is not an API function)
+            if ( !this.mapObject.moveByXY || 
+                 (typeof this.mapObject.moveByXY != "function" ) ) {
+
+                this.dragPanMapObject = null;
+            }                
+        } catch(e) {}
+    },
+
+    /**
+     * Method: onMapResize
+     * 
+     */
+    onMapResize: function() {
+        try {
+            var size = this.getMapObjectSizeFromOLSize(this.map.getSize());
+            this.mapObject.resizeTo(size);
+        } catch(e) {}     
+    },    
+    
+    
+    /** 
+     * APIMethod: setMap
+     * Overridden from EventPane because we need to remove this yahoo event
+     *     pane which prohibits our drag and drop, and we can only do this 
+     *     once the map has been loaded and centered.
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    setMap: function(map) {
+        OpenLayers.Layer.EventPane.prototype.setMap.apply(this, arguments);
+
+        this.map.events.register("moveend", this, this.fixYahooEventPane);
+    },
+
+    /** 
+     * Method: fixYahooEventPane
+     * The map has been centered, so the mysterious yahoo eventpane has been
+     *     added. we remove it so that it doesnt mess with *our* event pane.
+     */
+    fixYahooEventPane: function() {
+        var yahooEventPane = OpenLayers.Util.getElement("ygddfdiv");
+        if (yahooEventPane != null) {
+            if (yahooEventPane.parentNode != null) {
+                yahooEventPane.parentNode.removeChild(yahooEventPane);
+            }
+            this.map.events.unregister("moveend", this, 
+                                       this.fixYahooEventPane);
+        }
+    },
+
+    /** 
+     * APIMethod: getWarningHTML
+     * 
+     * Returns: 
+     * {String} String with information on why layer is broken, how to get
+     *          it working.
+     */
+    getWarningHTML:function() {
+        return OpenLayers.i18n(
+            "getLayerWarning", {'layerType':'Yahoo', 'layerLib':'Yahoo'}
+        );
+    },
+
+  /********************************************************/
+  /*                                                      */
+  /*             Translation Functions                    */
+  /*                                                      */
+  /*    The following functions translate GMaps and OL    */ 
+  /*     formats for Pixel, LonLat, Bounds, and Zoom      */
+  /*                                                      */
+  /********************************************************/
+
+
+  //
+  // TRANSLATION: MapObject Zoom <-> OpenLayers Zoom
+  //
+  
+    /**
+     * APIMethod: getOLZoomFromMapObjectZoom
+     * 
+     * Parameters:
+     * gZoom - {Integer}
+     * 
+     * Returns:
+     * {Integer} An OpenLayers Zoom level, translated from the passed in gZoom
+     *           Returns null if null value is passed in.
+     */
+    getOLZoomFromMapObjectZoom: function(moZoom) {
+        var zoom = null;
+        if (moZoom != null) {
+            zoom = OpenLayers.Layer.FixedZoomLevels.prototype.getOLZoomFromMapObjectZoom.apply(this, [moZoom]);
+            zoom = 18 - zoom;
+        }
+        return zoom;
+    },
+    
+    /**
+     * APIMethod: getMapObjectZoomFromOLZoom
+     * 
+     * Parameters:
+     * olZoom - {Integer}
+     * 
+     * Returns:
+     * {Integer} A MapObject level, translated from the passed in olZoom
+     *           Returns null if null value is passed in
+     */
+    getMapObjectZoomFromOLZoom: function(olZoom) {
+        var zoom = null; 
+        if (olZoom != null) {
+            zoom = OpenLayers.Layer.FixedZoomLevels.prototype.getMapObjectZoomFromOLZoom.apply(this, [olZoom]);
+            zoom = 18 - zoom;
+        }
+        return zoom;
+    },
+
+    /************************************
+     *                                  *
+     *   MapObject Interface Controls   *
+     *                                  *
+     ************************************/
+
+
+  // Get&Set Center, Zoom
+
+    /** 
+     * APIMethod: setMapObjectCenter
+     * Set the mapObject to the specified center and zoom
+     * 
+     * Parameters:
+     * center - {Object} MapObject LonLat format
+     * zoom - {int} MapObject zoom format
+     */
+    setMapObjectCenter: function(center, zoom) {
+        this.mapObject.drawZoomAndCenter(center, zoom); 
+    },
+   
+    /**
+     * APIMethod: getMapObjectCenter
+     * 
+     * Returns: 
+     * {Object} The mapObject's current center in Map Object format
+     */
+    getMapObjectCenter: function() {
+        return this.mapObject.getCenterLatLon();
+    },
+
+    /**
+     * APIMethod: dragPanMapObject
+     * 
+     * Parameters:
+     * dX - {Integer}
+     * dY - {Integer}
+     */
+    dragPanMapObject: function(dX, dY) {
+        this.mapObject.moveByXY({
+            'x': -dX,
+            'y': dY
+        });
+    },
+    
+    /** 
+     * APIMethod: getMapObjectZoom
+     * 
+     * Returns:
+     * {Integer} The mapObject's current zoom, in Map Object format
+     */
+    getMapObjectZoom: function() {
+        return this.mapObject.getZoomLevel();
+    },
+
+
+  // LonLat - Pixel Translation
+  
+    /**
+     * APIMethod: getMapObjectLonLatFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Object} MapObject LonLat translated from MapObject Pixel
+     */
+    getMapObjectLonLatFromMapObjectPixel: function(moPixel) {
+        return this.mapObject.convertXYLatLon(moPixel);
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Object} MapObject Pixel transtlated from MapObject LonLat
+     */
+    getMapObjectPixelFromMapObjectLonLat: function(moLonLat) {
+        return this.mapObject.convertLatLonXY(moLonLat);
+    },
+
+
+    /************************************
+     *                                  *
+     *       MapObject Primitives       *
+     *                                  *
+     ************************************/
+
+
+  // LonLat
+    
+    /**
+     * APIMethod: getLongitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Longitude of the given MapObject LonLat
+     */
+    getLongitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.Lon, moLonLat.Lat).lon :
+            moLonLat.Lon;
+    },
+
+    /**
+     * APIMethod: getLatitudeFromMapObjectLonLat
+     * 
+     * Parameters:
+     * moLonLat - {Object} MapObject LonLat format
+     * 
+     * Returns:
+     * {Float} Latitude of the given MapObject LonLat
+     */
+    getLatitudeFromMapObjectLonLat: function(moLonLat) {
+        return this.sphericalMercator ? 
+            this.forwardMercator(moLonLat.Lon, moLonLat.Lat).lat :
+            moLonLat.Lat;
+    },
+
+    /**
+     * APIMethod: getMapObjectLonLatFromLonLat
+     * 
+     * Parameters:
+     * lon - {Float}
+     * lat - {Float}
+     * 
+     * Returns:
+     * {Object} MapObject LonLat built from lon and lat params
+     */
+    getMapObjectLonLatFromLonLat: function(lon, lat) {
+        var yLatLong;
+        if(this.sphericalMercator) {
+            var lonlat = this.inverseMercator(lon, lat);
+            yLatLong = new YGeoPoint(lonlat.lat, lonlat.lon);
+        } else {
+            yLatLong = new YGeoPoint(lat, lon);
+        }
+        return yLatLong;
+    },
+
+  // Pixel
+    
+    /**
+     * APIMethod: getXFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} X value of the MapObject Pixel
+     */
+    getXFromMapObjectPixel: function(moPixel) {
+        return moPixel.x;
+    },
+
+    /**
+     * APIMethod: getYFromMapObjectPixel
+     * 
+     * Parameters:
+     * moPixel - {Object} MapObject Pixel format
+     * 
+     * Returns:
+     * {Integer} Y value of the MapObject Pixel
+     */
+    getYFromMapObjectPixel: function(moPixel) {
+        return moPixel.y;
+    },
+
+    /**
+     * APIMethod: getMapObjectPixelFromXY
+     * 
+     * Parameters:
+     * x - {Integer}
+     * y - {Integer}
+     * 
+     * Returns:
+     * {Object} MapObject Pixel from x and y parameters
+     */
+    getMapObjectPixelFromXY: function(x, y) {
+        return new YCoordPoint(x, y);
+    },
+    
+  // Size
+  
+    /**
+     * APIMethod: getMapObjectSizeFromOLSize
+     * 
+     * Parameters:
+     * olSize - {<OpenLayers.Size>}
+     * 
+     * Returns:
+     * {Object} MapObject Size from olSize parameter
+     */
+    getMapObjectSizeFromOLSize: function(olSize) {
+        return new YSize(olSize.w, olSize.h);
+    },
+    
+    CLASS_NAME: "OpenLayers.Layer.Yahoo"
+});
+/* ======================================================================
+    OpenLayers/Style.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+  * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Style
+ * This class represents a UserStyle obtained
+ *     from a SLD, containing styling rules.
+ */
+OpenLayers.Style = OpenLayers.Class({
+
+    /**
+     * APIProperty: name
+     * {String}
+     */
+    name: null,
+    
+    /**
+     * Property: title
+     * {String} Title of this style (set if included in SLD)
+     */
+    title: null,
+    
+    /**
+     * Property: description
+     * {String} Description of this style (set if abstract is included in SLD)
+     */
+    description: null,
+
+    /**
+     * APIProperty: layerName
+     * {<String>} name of the layer that this style belongs to, usually
+     * according to the NamedLayer attribute of an SLD document.
+     */
+    layerName: null,
+    
+    /**
+     * APIProperty: isDefault
+     * {Boolean}
+     */
+    isDefault: false,
+     
+    /** 
+     * Property: rules 
+     * {Array(<OpenLayers.Rule>)}
+     */
+    rules: null,
+    
+    /**
+     * Property: context
+     * {Object} An optional object with properties that symbolizers' property
+     * values should be evaluated against. If no context is specified,
+     * feature.attributes will be used
+     */
+    context: null,
+
+    /**
+     * Property: defaultStyle
+     * {Object} hash of style properties to use as default for merging
+     * rule-based style symbolizers onto. If no rules are defined,
+     * createSymbolizer will return this style.
+     */
+    defaultStyle: null,
+    
+    /**
+     * Property: propertyStyles
+     * {Hash of Boolean} cache of style properties that need to be parsed for
+     * propertyNames. Property names are keys, values won't be used.
+     */
+    propertyStyles: null,
+    
+
+    /** 
+     * Constructor: OpenLayers.Style
+     * Creates a UserStyle.
+     *
+     * Parameters:
+     * style        - {Object} Optional hash of style properties that will be
+     *                used as default style for this style object. This style
+     *                applies if no rules are specified. Symbolizers defined in
+     *                rules will extend this default style.
+     * options      - {Object} An optional object with properties to set on the
+     *                userStyle
+     * 
+     * Return:
+     * {<OpenLayers.Style>}
+     */
+    initialize: function(style, options) {
+        this.rules = [];
+
+        // use the default style from OpenLayers.Feature.Vector if no style
+        // was given in the constructor
+        this.setDefaultStyle(style || 
+                OpenLayers.Feature.Vector.style["default"]);
+        
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /** 
+     * APIMethod: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+        for (var i=0, len=this.rules.length; i<len; i++) {
+            this.rules[i].destroy();
+            this.rules[i] = null;
+        }
+        this.rules = null;
+        this.defaultStyle = null;
+    },
+    
+    /**
+     * Method: createSymbolizer
+     * creates a style by applying all feature-dependent rules to the base
+     * style.
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature>} feature to evaluate rules for
+     * 
+     * Returns:
+     * {Object} symbolizer hash
+     */
+    createSymbolizer: function(feature) {
+        var style = this.createLiterals(
+            OpenLayers.Util.extend({}, this.defaultStyle), feature);
+        
+        var rules = this.rules;
+
+        var rule, context;
+        var elseRules = [];
+        var appliedRules = false;
+        for(var i=0, len=rules.length; i<len; i++) {
+            rule = rules[i];
+            // does the rule apply?
+            var applies = rule.evaluate(feature);
+            
+            if(applies) {
+                if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
+                    elseRules.push(rule);
+                } else {
+                    appliedRules = true;
+                    this.applySymbolizer(rule, style, feature);
+                }
+            }
+        }
+        
+        // if no other rules apply, apply the rules with else filters
+        if(appliedRules == false && elseRules.length > 0) {
+            appliedRules = true;
+            for(var i=0, len=elseRules.length; i<len; i++) {
+                this.applySymbolizer(elseRules[i], style, feature);
+            }
+        }
+
+        // don't display if there were rules but none applied
+        if(rules.length > 0 && appliedRules == false) {
+            style.display = "none";
+        } else {
+            style.display = "";
+        }
+        
+        return style;
+    },
+    
+    /**
+     * Method: applySymbolizer
+     *
+     * Parameters:
+     * rule - {OpenLayers.Rule}
+     * style - {Object}
+     * feature - {<OpenLayer.Feature.Vector>}
+     *
+     * Returns:
+     * {Object} A style with new symbolizer applied.
+     */
+    applySymbolizer: function(rule, style, feature) {
+        var symbolizerPrefix = feature.geometry ?
+                this.getSymbolizerPrefix(feature.geometry) :
+                OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
+
+        var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
+
+        // merge the style with the current style
+        return this.createLiterals(
+                OpenLayers.Util.extend(style, symbolizer), feature);
+    },
+    
+    /**
+     * Method: createLiterals
+     * creates literals for all style properties that have an entry in
+     * <this.propertyStyles>.
+     * 
+     * Parameters:
+     * style   - {Object} style to create literals for. Will be modified
+     *           inline.
+     * feature - {Object}
+     * 
+     * Returns:
+     * {Object} the modified style
+     */
+    createLiterals: function(style, feature) {
+        var context = this.context || feature.attributes || feature.data;
+        
+        for (var i in this.propertyStyles) {
+            style[i] = OpenLayers.Style.createLiteral(style[i], context, feature);
+        }
+        return style;
+    },
+    
+    /**
+     * Method: findPropertyStyles
+     * Looks into all rules for this style and the defaultStyle to collect
+     * all the style hash property names containing ${...} strings that have
+     * to be replaced using the createLiteral method before returning them.
+     * 
+     * Returns:
+     * {Object} hash of property names that need createLiteral parsing. The
+     * name of the property is the key, and the value is true;
+     */
+    findPropertyStyles: function() {
+        var propertyStyles = {};
+
+        // check the default style
+        var style = this.defaultStyle;
+        this.addPropertyStyles(propertyStyles, style);
+
+        // walk through all rules to check for properties in their symbolizer
+        var rules = this.rules;
+        var symbolizer, value;
+        for (var i=0, len=rules.length; i<len; i++) {
+            var symbolizer = rules[i].symbolizer;
+            for (var key in symbolizer) {
+                value = symbolizer[key];
+                if (typeof value == "object") {
+                    // symbolizer key is "Point", "Line" or "Polygon"
+                    this.addPropertyStyles(propertyStyles, value);
+                } else {
+                    // symbolizer is a hash of style properties
+                    this.addPropertyStyles(propertyStyles, symbolizer);
+                    break;
+                }
+            }
+        }
+        return propertyStyles;
+    },
+    
+    /**
+     * Method: addPropertyStyles
+     * 
+     * Parameters:
+     * propertyStyles - {Object} hash to add new property styles to. Will be
+     *                  modified inline
+     * symbolizer     - {Object} search this symbolizer for property styles
+     * 
+     * Returns:
+     * {Object} propertyStyles hash
+     */
+    addPropertyStyles: function(propertyStyles, symbolizer) {
+        var property;
+        for (var key in symbolizer) {
+            property = symbolizer[key];
+            if (typeof property == "string" &&
+                    property.match(/\$\{\w+\}/)) {
+                propertyStyles[key] = true;
+            }
+        }
+        return propertyStyles;
+    },
+    
+    /**
+     * APIMethod: addRules
+     * Adds rules to this style.
+     * 
+     * Parameters:
+     * rules - {Array(<OpenLayers.Rule>)}
+     */
+    addRules: function(rules) {
+        this.rules = this.rules.concat(rules);
+        this.propertyStyles = this.findPropertyStyles();
+    },
+    
+    /**
+     * APIMethod: setDefaultStyle
+     * Sets the default style for this style object.
+     * 
+     * Parameters:
+     * style - {Object} Hash of style properties
+     */
+    setDefaultStyle: function(style) {
+        this.defaultStyle = style; 
+        this.propertyStyles = this.findPropertyStyles();
+    },
+        
+    /**
+     * Method: getSymbolizerPrefix
+     * Returns the correct symbolizer prefix according to the
+     * geometry type of the passed geometry
+     * 
+     * Parameters:
+     * geometry {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {String} key of the according symbolizer
+     */
+    getSymbolizerPrefix: function(geometry) {
+        var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
+        for (var i=0, len=prefixes.length; i<len; i++) {
+            if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
+                return prefixes[i];
+            }
+        }
+    },
+    
+    CLASS_NAME: "OpenLayers.Style"
+});
+
+
+/**
+ * Function: createLiteral
+ * converts a style value holding a combination of PropertyName and Literal
+ * into a Literal, taking the property values from the passed features.
+ * 
+ * Parameters:
+ * value - {String} value to parse. If this string contains a construct like
+ *         "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
+ *         will be replaced by the value of the "bar" attribute of the passed
+ *         feature.
+ * context - {Object} context to take attribute values from
+ * feature - {OpenLayers.Feature.Vector} The feature that will be passed
+ *     to <OpenLayers.String.format> for evaluating functions in the context.
+ * 
+ * Returns:
+ * {String} the parsed value. In the example of the value parameter above, the
+ * result would be "foo valueOfBar", assuming that the passed feature has an
+ * attribute named "bar" with the value "valueOfBar".
+ */
+OpenLayers.Style.createLiteral = function(value, context, feature) {
+    if (typeof value == "string" && value.indexOf("${") != -1) {
+        value = OpenLayers.String.format(value, context, [feature]);
+        value = (isNaN(value) || !value) ? value : parseFloat(value);
+    }
+    return value;
+};
+    
+/**
+ * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES
+ * {Array} prefixes of the sld symbolizers. These are the
+ * same as the main geometry types
+ */
+OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text'];
+/* ======================================================================
+    OpenLayers/Control/ModifyFeature.js
+   ====================================================================== */
+
+/* Copyright (c) 2006 MetaCarta, Inc., published under the Clear BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt 
+ * for the full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Control/DragFeature.js
+ * @requires OpenLayers/Control/SelectFeature.js
+ * @requires OpenLayers/Handler/Keyboard.js
+ */
+
+/**
+ * Class: OpenLayers.Control.ModifyFeature
+ * Control to modify features.  When activated, a click renders the vertices
+ *     of a feature - these vertices can then be dragged.  By default, the
+ *     delete key will delete the vertex under the mouse.  New features are
+ *     added by dragging "virtual vertices" between vertices.  Create a new
+ *     control with the <OpenLayers.Control.ModifyFeature> constructor.
+ *
+ * Inherits From:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.ModifyFeature = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * APIProperty: geometryTypes
+     * {Array(String)} To restrict modification to a limited set of geometry
+     *     types, send a list of strings corresponding to the geometry class
+     *     names.
+     */
+    geometryTypes: null,
+
+    /**
+     * APIProperty: clickout
+     * {Boolean} Unselect features when clicking outside any feature.
+     *     Default is true.
+     */
+    clickout: true,
+
+    /**
+     * APIProperty: toggle
+     * {Boolean} Unselect a selected feature on click.
+     *      Default is true.
+     */
+    toggle: true,
+
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>}
+     */
+    layer: null,
+    
+    /**
+     * Property: feature
+     * {<OpenLayers.Feature.Vector>} Feature currently available for modification.
+     */
+    feature: null,
+    
+    /**
+     * Property: vertices
+     * {Array(<OpenLayers.Feature.Vector>)} Verticies currently available
+     *     for dragging.
+     */
+    vertices: null,
+    
+    /**
+     * Property: virtualVertices
+     * {Array(<OpenLayers.Feature.Vector>)} Virtual vertices in the middle
+     *     of each edge.
+     */
+    virtualVertices: null,
+
+    /**
+     * Property: selectControl
+     * {<OpenLayers.Control.SelectFeature>}
+     */
+    selectControl: null,
+    
+    /**
+     * Property: dragControl
+     * {<OpenLayers.Control.DragFeature>}
+     */
+    dragControl: null,
+    
+    /**
+     * Property: handlers
+     * {Object}
+     */
+    handlers: null,
+    
+    /**
+     * APIProperty: deleteCodes
+     * {Array(Integer)} Keycodes for deleting verticies.  Set to null to disable
+     *     vertex deltion by keypress.  If non-null, keypresses with codes
+     *     in this array will delete vertices under the mouse. Default
+     *     is 46 and 68, the 'delete' and lowercase 'd' keys.
+     */
+    deleteCodes: null,
+
+    /**
+     * APIProperty: virtualStyle
+     * {Object} A symbolizer to be used for virtual vertices.
+     */
+    virtualStyle: null,
+
+    /**
+     * APIProperty: mode
+     * {Integer} Bitfields specifying the modification mode. Defaults to
+     *      OpenLayers.Control.ModifyFeature.RESHAPE. To set the mode to a
+     *      combination of options, use the | operator. or example, to allow
+     *      the control to both resize and rotate features, use the following
+     *      syntax
+     * (code)
+     * control.mode = OpenLayers.Control.ModifyFeature.RESIZE |
+     *                OpenLayers.Control.ModifyFeature.ROTATE;
+     *  (end)
+     */
+    mode: null,
+
+    /**
+     * Property: radiusHandle
+     * {<OpenLayers.Feature.Vector>} A handle for rotating/resizing a feature.
+     */
+    radiusHandle: null,
+
+    /**
+     * Property: dragHandle
+     * {<OpenLayers.Feature.Vector>} A handle for dragging a feature.
+     */
+    dragHandle: null,
+
+    /**
+     * APIProperty: onModificationStart 
+     * {Function} *Deprecated*.  Register for "beforefeaturemodified" instead.
+     *     The "beforefeaturemodified" event is triggered on the layer before
+     *     any modification begins.
+     *
+     * Optional function to be called when a feature is selected
+     *     to be modified. The function should expect to be called with a
+     *     feature.  This could be used for example to allow to lock the
+     *     feature on server-side.
+     */
+    onModificationStart: function() {},
+
+    /**
+     * APIProperty: onModification
+     * {Function} *Deprecated*.  Register for "featuremodified" instead.
+     *     The "featuremodified" event is triggered on the layer with each
+     *     feature modification.
+     *
+     * Optional function to be called when a feature has been
+     *     modified.  The function should expect to be called with a feature.
+     */
+    onModification: function() {},
+
+    /**
+     * APIProperty: onModificationEnd
+     * {Function} *Deprecated*.  Register for "afterfeaturemodified" instead.
+     *     The "afterfeaturemodified" event is triggered on the layer after
+     *     a feature has been modified.
+     *
+     * Optional function to be called when a feature is finished 
+     *     being modified.  The function should expect to be called with a
+     *     feature.
+     */
+    onModificationEnd: function() {},
+
+    /**
+     * Constructor: OpenLayers.Control.ModifyFeature
+     * Create a new modify feature control.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.Vector>} Layer that contains features that
+     *     will be modified.
+     * options - {Object} Optional object whose properties will be set on the
+     *     control.
+     */
+    initialize: function(layer, options) {
+        this.layer = layer;
+        this.vertices = [];
+        this.virtualVertices = [];
+        this.virtualStyle = OpenLayers.Util.extend({},
+            this.layer.style || this.layer.styleMap.createSymbolizer());
+        this.virtualStyle.fillOpacity = 0.3;
+        this.virtualStyle.strokeOpacity = 0.3;
+        this.deleteCodes = [46, 68];
+        this.mode = OpenLayers.Control.ModifyFeature.RESHAPE;
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        if(!(this.deleteCodes instanceof Array)) {
+            this.deleteCodes = [this.deleteCodes];
+        }
+        var control = this;
+
+        // configure the select control
+        var selectOptions = {
+            geometryTypes: this.geometryTypes,
+            clickout: this.clickout,
+            toggle: this.toggle
+        };
+        this.selectControl = new OpenLayers.Control.SelectFeature(
+            layer, selectOptions
+        );
+        this.layer.events.on({
+            "beforefeatureselected": this.beforeSelectFeature,
+            "featureselected": this.selectFeature,
+            "featureunselected": this.unselectFeature,
+            scope: this
+        });
+
+        // configure the drag control
+        var dragOptions = {
+            geometryTypes: ["OpenLayers.Geometry.Point"],
+            snappingOptions: this.snappingOptions,
+            onStart: function(feature, pixel) {
+                control.dragStart.apply(control, [feature, pixel]);
+            },
+            onDrag: function(feature) {
+                control.dragVertex.apply(control, [feature]);
+            },
+            onComplete: function(feature) {
+                control.dragComplete.apply(control, [feature]);
+            }
+        };
+        this.dragControl = new OpenLayers.Control.DragFeature(
+            layer, dragOptions
+        );
+
+        // configure the keyboard handler
+        var keyboardOptions = {
+            keydown: this.handleKeypress
+        };
+        this.handlers = {
+            keyboard: new OpenLayers.Handler.Keyboard(this, keyboardOptions)
+        };
+    },
+
+    /**
+     * APIMethod: destroy
+     * Take care of things that are not handled in superclass.
+     */
+    destroy: function() {
+        this.layer.events.un({
+            "beforefeatureselected": this.beforeSelectFeature,
+            "featureselected": this.selectFeature,
+            "featureunselected": this.unselectFeature,
+            scope: this
+        });
+        this.layer = null;
+        this.selectControl.destroy();
+        this.dragControl.destroy();
+        OpenLayers.Control.prototype.destroy.apply(this, []);
+    },
+
+    /**
+     * APIMethod: activate
+     * Activate the control.
+     * 
+     * Returns:
+     * {Boolean} Successfully activated the control.
+     */
+    activate: function() {
+        return (this.selectControl.activate() &&
+                this.handlers.keyboard.activate() &&
+                OpenLayers.Control.prototype.activate.apply(this, arguments));
+    },
+
+    /**
+     * APIMethod: deactivate
+     * Deactivate the control.
+     *
+     * Returns: 
+     * {Boolean} Successfully deactivated the control.
+     */
+    deactivate: function() {
+        var deactivated = false;
+        // the return from the controls is unimportant in this case
+        if(OpenLayers.Control.prototype.deactivate.apply(this, arguments)) {
+            this.layer.removeFeatures(this.vertices, {silent: true});
+            this.layer.removeFeatures(this.virtualVertices, {silent: true});
+            this.vertices = [];
+            this.dragControl.deactivate();
+            if(this.feature && this.feature.geometry) {
+                this.selectControl.unselect.apply(this.selectControl,
+                                                  [this.feature]);
+            }
+            this.selectControl.deactivate();
+            this.handlers.keyboard.deactivate();
+            deactivated = true;
+        }
+        return deactivated;
+    },
+    
+    /**
+     * Method: beforeSelectFeature
+     * Called before a feature is selected.
+     *
+     * Parameters:
+     * object - {Object} Object with a feature property referencing the
+     *     selected feature.
+     */
+    beforeSelectFeature: function(object) {
+        return this.layer.events.triggerEvent(
+            "beforefeaturemodified", {feature: object.feature}
+        );
+    },
+
+    /**
+     * Method: selectFeature
+     * Called when the select feature control selects a feature.
+     *
+     * Parameters:
+     * object - {Object} Object with a feature property referencing the
+     *     selected feature.
+     */
+    selectFeature: function(object) {
+        this.feature = object.feature;
+        this.resetVertices();
+        this.dragControl.activate();
+        this.onModificationStart(this.feature);
+    },
+
+    /**
+     * Method: unselectFeature
+     * Called when the select feature control unselects a feature.
+     *
+     * Parameters:
+     * object - {Object} Object with a feature property referencing the
+     *     unselected feature.
+     */
+    unselectFeature: function(object) {
+        this.layer.removeFeatures(this.vertices, {silent: true});
+        this.vertices = [];
+        this.layer.destroyFeatures(this.virtualVertices, {silent: true});
+        this.virtualVertices = [];
+        if(this.dragHandle) {
+            this.layer.destroyFeatures([this.dragHandle], {silent: true});
+            delete this.dragHandle;
+        }
+        if(this.radiusHandle) {
+            this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+            delete this.radiusHandle;
+        }
+        this.feature = null;
+        this.dragControl.deactivate();
+        this.onModificationEnd(object.feature);
+        this.layer.events.triggerEvent("afterfeaturemodified", 
+                                       {feature: object.feature});
+    },
+
+    /**
+     * Method: dragStart
+     * Called by the drag feature control with before a feature is dragged.
+     *     This method is used to differentiate between points and vertices
+     *     of higher order geometries.  This respects the <geometryTypes>
+     *     property and forces a select of points when the drag control is
+     *     already active (and stops events from propagating to the select
+     *     control).
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The point or vertex about to be
+     *     dragged.
+     * pixel - {<OpenLayers.Pixel>} Pixel location of the mouse event.
+     */
+    dragStart: function(feature, pixel) {
+        // only change behavior if the feature is not in the vertices array
+        if(feature != this.feature && !feature.geometry.parent &&
+           feature != this.dragHandle && feature != this.radiusHandle) {
+            if(this.feature) {
+                // unselect the currently selected feature
+                this.selectControl.clickFeature.apply(this.selectControl,
+                                                      [this.feature]);
+            }
+            // check any constraints on the geometry type
+            if(this.geometryTypes == null ||
+               OpenLayers.Util.indexOf(this.geometryTypes,
+                                       feature.geometry.CLASS_NAME) != -1) {
+                // select the point
+                this.selectControl.clickFeature.apply(this.selectControl,
+                                                      [feature]);
+                /**
+                 * TBD: These lines improve workflow by letting the user
+                 *     immediately start dragging after the mouse down.
+                 *     However, it is very ugly to be messing with controls
+                 *     and their handlers in this way.  I'd like a better
+                 *     solution if the workflow change is necessary.
+                 */
+                // prepare the point for dragging
+                this.dragControl.overFeature.apply(this.dragControl,
+                                                   [feature]);
+                this.dragControl.lastPixel = pixel;
+                this.dragControl.handlers.drag.started = true;
+                this.dragControl.handlers.drag.start = pixel;
+                this.dragControl.handlers.drag.last = pixel;
+            }
+        }
+    },
+    
+    /**
+     * Method: dragVertex
+     * Called by the drag feature control with each drag move of a vertex.
+     *
+     * Parameters:
+     * vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+     */
+    dragVertex: function(vertex) {
+        /**
+         * Five cases:
+         * 1) dragging a simple point
+         * 2) dragging a virtual vertex
+         * 3) dragging a drag handle
+         * 4) dragging a radius handle
+         * 5) dragging a real vertex
+         */
+        if(this.feature.geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+            // dragging a simple point
+            if(this.feature != vertex) {
+                this.feature = vertex;
+            }
+        } else {
+            if(vertex._index) {
+                // dragging a virtual vertex
+                vertex.geometry.parent.addComponent(vertex.geometry,
+                                                    vertex._index);
+                // move from virtual to real vertex
+                delete vertex._index;
+                OpenLayers.Util.removeItem(this.virtualVertices, vertex);
+                this.vertices.push(vertex);
+            } else if(vertex == this.dragHandle) {
+                // dragging a drag handle
+                this.layer.removeFeatures(this.vertices, {silent: true});
+                this.vertices = [];
+                if(this.radiusHandle) {
+                    this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+                    this.radiusHandle = null;
+                }
+            }
+            // dragging a radius handle - no special treatment
+            // dragging a real vertex - no special treatment
+            if(this.virtualVertices.length > 0) {
+                this.layer.destroyFeatures(this.virtualVertices, {silent: true});
+                this.virtualVertices = [];
+            }
+            this.layer.drawFeature(this.feature, this.selectControl.renderIntent);
+        }
+        // keep the vertex on top so it gets the mouseout after dragging
+        // this should be removed in favor of an option to draw under or
+        // maintain node z-index
+        this.layer.drawFeature(vertex);
+    },
+    
+    /**
+     * Method: dragComplete
+     * Called by the drag feature control when the feature dragging is complete.
+     *
+     * Parameters:
+     * vertex - {<OpenLayers.Feature.Vector>} The vertex being dragged.
+     */
+    dragComplete: function(vertex) {
+        this.resetVertices();
+        this.onModification(this.feature);
+        this.layer.events.triggerEvent("featuremodified", 
+                                       {feature: this.feature});
+    },
+    
+    /**
+     * Method: resetVertices
+     */
+    resetVertices: function() {
+        // if coming from a drag complete we're about to destroy the vertex
+        // that was just dragged. For that reason, the drag feature control
+        // will never detect a mouse-out on that vertex, meaning that the drag
+        // handler won't be deactivated. This can cause errors because the drag
+        // feature control still has a feature to drag but that feature is
+        // destroyed. To prevent this, we call outFeature on the drag feature
+        // control if the control actually has a feature to drag.
+        if(this.dragControl.feature) {
+            this.dragControl.outFeature(this.dragControl.feature);
+        }
+        if(this.vertices.length > 0) {
+            this.layer.removeFeatures(this.vertices, {silent: true});
+            this.vertices = [];
+        }
+        if(this.virtualVertices.length > 0) {
+            this.layer.removeFeatures(this.virtualVertices, {silent: true});
+            this.virtualVertices = [];
+        }
+        if(this.dragHandle) {
+            this.layer.destroyFeatures([this.dragHandle], {silent: true});
+            this.dragHandle = null;
+        }
+        if(this.radiusHandle) {
+            this.layer.destroyFeatures([this.radiusHandle], {silent: true});
+            this.radiusHandle = null;
+        }
+        if(this.feature &&
+           this.feature.geometry.CLASS_NAME != "OpenLayers.Geometry.Point") {
+            if((this.mode & OpenLayers.Control.ModifyFeature.DRAG)) {
+                this.collectDragHandle();
+            }
+            if((this.mode & (OpenLayers.Control.ModifyFeature.ROTATE |
+                             OpenLayers.Control.ModifyFeature.RESIZE))) {
+                this.collectRadiusHandle();
+            }
+            if((this.mode & OpenLayers.Control.ModifyFeature.RESHAPE)) {
+                this.collectVertices();
+            }
+        }
+    },
+    
+    /**
+     * Method: handleKeypress
+     * Called by the feature handler on keypress.  This is used to delete
+     *     vertices. If the <deleteCode> property is set, vertices will
+     *     be deleted when a feature is selected for modification and
+     *     the mouse is over a vertex.
+     *
+     * Parameters:
+     * {Integer} Key code corresponding to the keypress event.
+     */
+    handleKeypress: function(evt) {
+        var code = evt.keyCode;
+        
+        // check for delete key
+        if(this.feature &&
+           OpenLayers.Util.indexOf(this.deleteCodes, code) != -1) {
+            var vertex = this.dragControl.feature;
+            if(vertex &&
+               OpenLayers.Util.indexOf(this.vertices, vertex) != -1 &&
+               !this.dragControl.handlers.drag.dragging &&
+               vertex.geometry.parent) {
+                // remove the vertex
+                vertex.geometry.parent.removeComponent(vertex.geometry);
+                this.layer.drawFeature(this.feature,
+                                       this.selectControl.renderIntent);
+                this.resetVertices();
+                this.onModification(this.feature);
+                this.layer.events.triggerEvent("featuremodified", 
+                                               {feature: this.feature});
+            }
+        }
+    },
+
+    /**
+     * Method: collectVertices
+     * Collect the vertices from the modifiable feature's geometry and push
+     *     them on to the control's vertices array.
+     */
+    collectVertices: function() {
+        this.vertices = [];
+        this.virtualVertices = [];        
+        var control = this;
+        function collectComponentVertices(geometry) {
+            var i, vertex, component, len;
+            if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+                vertex = new OpenLayers.Feature.Vector(geometry);
+                control.vertices.push(vertex);
+            } else {
+                var numVert = geometry.components.length;
+                if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+                    numVert -= 1;
+                }
+                for(i=0; i<numVert; ++i) {
+                    component = geometry.components[i];
+                    if(component.CLASS_NAME == "OpenLayers.Geometry.Point") {
+                        vertex = new OpenLayers.Feature.Vector(component);
+                        control.vertices.push(vertex);
+                    } else {
+                        collectComponentVertices(component);
+                    }
+                }
+                
+                // add virtual vertices in the middle of each edge
+                if(geometry.CLASS_NAME != "OpenLayers.Geometry.MultiPoint") {
+                    for(i=0, len=geometry.components.length; i<len-1; ++i) {
+                        var prevVertex = geometry.components[i];
+                        var nextVertex = geometry.components[i + 1];
+                        if(prevVertex.CLASS_NAME == "OpenLayers.Geometry.Point" &&
+                           nextVertex.CLASS_NAME == "OpenLayers.Geometry.Point") {
+                            var x = (prevVertex.x + nextVertex.x) / 2;
+                            var y = (prevVertex.y + nextVertex.y) / 2;
+                            var point = new OpenLayers.Feature.Vector(
+                                new OpenLayers.Geometry.Point(x, y),
+                                null, control.virtualStyle
+                            );
+                            // set the virtual parent and intended index
+                            point.geometry.parent = geometry;
+                            point._index = i + 1;
+                            control.virtualVertices.push(point);
+                        }
+                    }
+                }
+            }
+        }
+        collectComponentVertices.call(this, this.feature.geometry);
+        this.layer.addFeatures(this.virtualVertices, {silent: true});
+        this.layer.addFeatures(this.vertices, {silent: true});
+    },
+
+    /**
+     * Method: collectDragHandle
+     * Collect the drag handle for the selected geometry.
+     */
+    collectDragHandle: function() {
+        var geometry = this.feature.geometry;
+        var center = geometry.getBounds().getCenterLonLat();
+        var originGeometry = new OpenLayers.Geometry.Point(
+            center.lon, center.lat
+        );
+        var origin = new OpenLayers.Feature.Vector(originGeometry);
+        originGeometry.move = function(x, y) {
+            OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+            geometry.move(x, y);
+        };
+        this.dragHandle = origin;
+        this.layer.addFeatures([this.dragHandle], {silent: true});
+    },
+
+    /**
+     * Method: collectRadiusHandle
+     * Collect the radius handle for the selected geometry.
+     */
+    collectRadiusHandle: function() {
+        var geometry = this.feature.geometry;
+        var bounds = geometry.getBounds();
+        var center = bounds.getCenterLonLat();
+        var originGeometry = new OpenLayers.Geometry.Point(
+            center.lon, center.lat
+        );
+        var radiusGeometry = new OpenLayers.Geometry.Point(
+            bounds.right, bounds.bottom
+        );
+        var radius = new OpenLayers.Feature.Vector(radiusGeometry);
+        var resize = (this.mode & OpenLayers.Control.ModifyFeature.RESIZE);
+        var rotate = (this.mode & OpenLayers.Control.ModifyFeature.ROTATE);
+        radiusGeometry.move = function(x, y) {
+            OpenLayers.Geometry.Point.prototype.move.call(this, x, y);
+            var dx1 = this.x - originGeometry.x;
+            var dy1 = this.y - originGeometry.y;
+            var dx0 = dx1 - x;
+            var dy0 = dy1 - y;
+            if(rotate) {
+                var a0 = Math.atan2(dy0, dx0);
+                var a1 = Math.atan2(dy1, dx1);
+                var angle = a1 - a0;
+                angle *= 180 / Math.PI;
+                geometry.rotate(angle, originGeometry);
+            }
+            if(resize) {
+                var l0 = Math.sqrt((dx0 * dx0) + (dy0 * dy0));
+                var l1 = Math.sqrt((dx1 * dx1) + (dy1 * dy1));
+                geometry.resize(l1 / l0, originGeometry);
+            }
+        };
+        this.radiusHandle = radius;
+        this.layer.addFeatures([this.radiusHandle], {silent: true});
+    },
+
+    /**
+     * Method: setMap
+     * Set the map property for the control and all handlers.
+     *
+     * Parameters:
+     * map - {<OpenLayers.Map>} The control's map.
+     */
+    setMap: function(map) {
+        this.selectControl.setMap(map);
+        this.dragControl.setMap(map);
+        OpenLayers.Control.prototype.setMap.apply(this, arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Control.ModifyFeature"
+});
+
+/**
+ * Constant: RESHAPE
+ * {Integer} Constant used to make the control work in reshape mode
+ */
+OpenLayers.Control.ModifyFeature.RESHAPE = 1;
+/**
+ * Constant: RESIZE
+ * {Integer} Constant used to make the control work in resize mode
+ */
+OpenLayers.Control.ModifyFeature.RESIZE = 2;
+/**
+ * Constant: ROTATE
+ * {Integer} Constant used to make the control work in rotate mode
+ */
+OpenLayers.Control.ModifyFeature.ROTATE = 4;
+/**
+ * Constant: DRAG
+ * {Integer} Constant used to make the control work in drag mode
+ */
+OpenLayers.Control.ModifyFeature.DRAG = 8;
+/* ======================================================================
     OpenLayers/Control/Navigation.js
    ====================================================================== */
 
@@ -14711,6 +34938,12 @@
      */
     dragPan: null,
 
+    /**
+     * APIProprety: dragPanOptions
+     * {Object} Options passed to the DragPan control.
+     */
+    dragPanOptions: null,
+
     /** 
      * Property: zoomBox
      * {<OpenLayers.Control.ZoomBox>}
@@ -14724,6 +34957,12 @@
     zoomWheelEnabled: true, 
 
     /**
+     * APIProperty: handleRightClicks
+     * {Boolean} Whether or not to handle right clicks. Default is false.
+     */
+    handleRightClicks: true,
+    
+    /**
      * Constructor: OpenLayers.Control.Navigation
      * Create a new navigation control
      * 
@@ -14785,13 +35024,25 @@
      * Method: draw
      */
     draw: function() {
-        this.handlers.click = new OpenLayers.Handler.Click(this, 
-                                        { 'dblclick': this.defaultDblClick },
-                                        {
-                                          'double': true, 
-                                          'stopDouble': true
-                                        });
-        this.dragPan = new OpenLayers.Control.DragPan({map: this.map});
+        // disable right mouse context menu for support of right click events
+        if (this.handleRightClicks) {
+            this.map.div.oncontextmenu = function () { return false;};
+        }
+
+        var clickCallbacks = { 
+            'dblclick': this.defaultDblClick, 
+            'dblrightclick': this.defaultDblRightClick 
+        };
+        var clickOptions = {
+            'double': true, 
+            'stopDouble': true
+        };
+        this.handlers.click = new OpenLayers.Handler.Click(
+            this, clickCallbacks, clickOptions
+        );
+        this.dragPan = new OpenLayers.Control.DragPan(
+            OpenLayers.Util.extend({map: this.map}, this.dragPanOptions)
+        );
         this.zoomBox = new OpenLayers.Control.ZoomBox(
                     {map: this.map, keyMask: OpenLayers.Handler.MOD_SHIFT});
         this.dragPan.draw();
@@ -14814,6 +35065,17 @@
     },
 
     /**
+     * Method: defaultRightDblClick 
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    defaultDblRightClick: function (evt) {
+        var newCenter = this.map.getLonLatFromViewPortPx( evt.xy ); 
+        this.map.setCenter(newCenter, this.map.zoom - 1);
+    },
+    
+    /**
      * Method: wheelChange  
      *
      * Parameters:
@@ -14881,6 +35143,546 @@
     CLASS_NAME: "OpenLayers.Control.Navigation"
 });
 /* ======================================================================
+    OpenLayers/Filter.js
+   ====================================================================== */
+
+/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Filter
+ * This class represents an OGC Filter.
+ */
+OpenLayers.Filter = OpenLayers.Class({
+    
+    /** 
+     * Constructor: OpenLayers.Filter
+     * This is an abstract class.  Create an instance of a filter subclass.
+     *
+     * Parameters:
+     * options - {Object} Optional object whose properties will be set on the
+     *     instance.
+     * 
+     * Returns:
+     * {<OpenLayers.Filter>}
+     */
+    initialize: function(options) {
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /** 
+     * APIMethod: destroy
+     * Remove reference to anything added.
+     */
+    destroy: function() {
+    },
+
+    /**
+     * APIMethod: evaluate
+     * Evaluates this filter in a specific context.  Should be implemented by
+     *     subclasses.
+     * 
+     * Parameters:
+     * context - {Object} Context to use in evaluating the filter.
+     * 
+     * Returns:
+     * {Boolean} The filter applies.
+     */
+    evaluate: function(context) {
+        return true;
+    },
+    
+    CLASS_NAME: "OpenLayers.Filter"
+});
+/* ======================================================================
+    OpenLayers/Geometry.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+ 
+/**
+ * @requires OpenLayers/Format/WKT.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry
+ * A Geometry is a description of a geographic object.  Create an instance of
+ * this class with the <OpenLayers.Geometry> constructor.  This is a base class,
+ * typical geometry types are described by subclasses of this class.
+ */
+OpenLayers.Geometry = OpenLayers.Class({
+
+    /**
+     * Property: id
+     * {String} A unique identifier for this geometry.
+     */
+    id: null,
+
+    /**
+     * Property: parent
+     * {<OpenLayers.Geometry>}This is set when a Geometry is added as component
+     * of another geometry
+     */
+    parent: null,
+
+    /**
+     * Property: bounds 
+     * {<OpenLayers.Bounds>} The bounds of this geometry
+     */
+    bounds: null,
+
+    /**
+     * Constructor: OpenLayers.Geometry
+     * Creates a geometry object.  
+     */
+    initialize: function() {
+        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_");
+    },
+    
+    /**
+     * Method: destroy
+     * Destroy this geometry.
+     */
+    destroy: function() {
+        this.id = null;
+        this.bounds = null;
+    },
+    
+    /**
+     * APIMethod: clone
+     * Create a clone of this geometry.  Does not set any non-standard
+     *     properties of the cloned geometry.
+     * 
+     * Returns:
+     * {<OpenLayers.Geometry>} An exact clone of this geometry.
+     */
+    clone: function() {
+        return new OpenLayers.Geometry();
+    },
+    
+    /**
+     * Set the bounds for this Geometry.
+     * 
+     * Parameters:
+     * object - {<OpenLayers.Bounds>} 
+     */
+    setBounds: function(bounds) {
+        if (bounds) {
+            this.bounds = bounds.clone();
+        }
+    },
+    
+    /**
+     * Method: clearBounds
+     * Nullify this components bounds and that of its parent as well.
+     */
+    clearBounds: function() {
+        this.bounds = null;
+        if (this.parent) {
+            this.parent.clearBounds();
+        }    
+    },
+    
+    /**
+     * Method: extendBounds
+     * Extend the existing bounds to include the new bounds. 
+     * If geometry's bounds is not yet set, then set a new Bounds.
+     * 
+     * Parameters:
+     * newBounds - {<OpenLayers.Bounds>} 
+     */
+    extendBounds: function(newBounds){
+        var bounds = this.getBounds();
+        if (!bounds) {
+            this.setBounds(newBounds);
+        } else {
+            this.bounds.extend(newBounds);
+        }
+    },
+    
+    /**
+     * APIMethod: getBounds
+     * Get the bounds for this Geometry. If bounds is not set, it 
+     * is calculated again, this makes queries faster.
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>}
+     */
+    getBounds: function() {
+        if (this.bounds == null) {
+            this.calculateBounds();
+        }
+        return this.bounds;
+    },
+    
+    /** 
+     * APIMethod: calculateBounds
+     * Recalculate the bounds for the geometry. 
+     */
+    calculateBounds: function() {
+        //
+        // This should be overridden by subclasses.
+        //
+    },
+    
+    /**
+     * Method: atPoint
+     * Note - This is only an approximation based on the bounds of the 
+     * geometry.
+     * 
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>} 
+     * toleranceLon - {float} Optional tolerance in Geometric Coords
+     * toleranceLat - {float} Optional tolerance in Geographic Coords
+     * 
+     * Returns:
+     * {Boolean} Whether or not the geometry is at the specified location
+     */
+    atPoint: function(lonlat, toleranceLon, toleranceLat) {
+        var atPoint = false;
+        var bounds = this.getBounds();
+        if ((bounds != null) && (lonlat != null)) {
+
+            var dX = (toleranceLon != null) ? toleranceLon : 0;
+            var dY = (toleranceLat != null) ? toleranceLat : 0;
+    
+            var toleranceBounds = 
+                new OpenLayers.Bounds(this.bounds.left - dX,
+                                      this.bounds.bottom - dY,
+                                      this.bounds.right + dX,
+                                      this.bounds.top + dY);
+
+            atPoint = toleranceBounds.containsLonLat(lonlat);
+        }
+        return atPoint;
+    },
+    
+    /**
+     * Method: getLength
+     * Calculate the length of this geometry. This method is defined in
+     * subclasses.
+     * 
+     * Returns:
+     * {Float} The length of the collection by summing its parts
+     */
+    getLength: function() {
+        //to be overridden by geometries that actually have a length
+        //
+        return 0.0;
+    },
+
+    /**
+     * Method: getArea
+     * Calculate the area of this geometry. This method is defined in subclasses.
+     * 
+     * Returns:
+     * {Float} The area of the collection by summing its parts
+     */
+    getArea: function() {
+        //to be overridden by geometries that actually have an area
+        //
+        return 0.0;
+    },
+
+    /**
+     * Method: toString
+     * Returns the Well-Known Text representation of a geometry
+     *
+     * Returns:
+     * {String} Well-Known Text
+     */
+    toString: function() {
+        return OpenLayers.Format.WKT.prototype.write(
+            new OpenLayers.Feature.Vector(this)
+        );
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry"
+});
+
+    
+/**
+ * Method: OpenLayers.Geometry.segmentsIntersect
+ * Determine whether two line segments intersect.  Optionally calculates
+ *     and returns the intersection point.  This function is optimized for
+ *     cases where seg1.x2 >= seg2.x1 || seg2.x2 >= seg1.x1.  In those
+ *     obvious cases where there is no intersection, the function should
+ *     not be called.
+ *
+ * Parameters:
+ * seg1 - {Object} Object representing a segment with properties x1, y1, x2,
+ *     and y2.  The start point is represented by x1 and y1.  The end point
+ *     is represented by x2 and y2.  Start and end are ordered so that x1 < x2.
+ * seg2 - {Object} Object representing a segment with properties x1, y1, x2,
+ *     and y2.  The start point is represented by x1 and y1.  The end point
+ *     is represented by x2 and y2.  Start and end are ordered so that x1 < x2.
+ * point - {Boolean} Return the intersection point.  If false, the actual
+ *     intersection point will not be calculated.  If true and the segments
+ *     intersect, the intersection point will be returned.  If true and
+ *     the segments do not intersect, false will be returned.  If true and
+ *     the segments are coincident, true will be returned.
+ *
+ * Returns:
+ * {Boolean | <OpenLayers.Geometry.Point>}  The two segments intersect.
+ *     If the point argument is true, the return will be the intersection
+ *     point or false if none exists.  If point is true and the segments
+ *     are coincident, return will be true (and the instersection is equal
+ *     to the shorter segment).
+ */
+OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, point) {
+    var intersection = false;
+    var x11_21 = seg1.x1 - seg2.x1;
+    var y11_21 = seg1.y1 - seg2.y1;
+    var x12_11 = seg1.x2 - seg1.x1;
+    var y12_11 = seg1.y2 - seg1.y1;
+    var y22_21 = seg2.y2 - seg2.y1;
+    var x22_21 = seg2.x2 - seg2.x1;
+    var d = (y22_21 * x12_11) - (x22_21 * y12_11);
+    var n1 = (x22_21 * y11_21) - (y22_21 * x11_21);
+    var n2 = (x12_11 * y11_21) - (y12_11 * x11_21);
+    if(d == 0) {
+        // parallel
+        if(n1 == 0 && n2 == 0) {
+            // coincident
+            intersection = true;
+        }
+    } else {
+        var along1 = n1 / d;
+        var along2 = n2 / d;
+        if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) {
+            // intersect
+            if(!point) {
+                intersection = true;
+            } else {
+                // calculate the intersection point
+                var x = seg1.x1 + (along1 * x12_11);
+                var y = seg1.y1 + (along1 * y12_11);
+                intersection = new OpenLayers.Geometry.Point(x, y);
+            }
+        }
+    }
+    return intersection;
+};
+/* ======================================================================
+    OpenLayers/Layer/KaMap.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.KaMap
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.KaMap = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+    /** 
+     * APIProperty: isBaseLayer
+     * {Boolean} KaMap Layer is always a base layer 
+     */    
+    isBaseLayer: true,
+
+    /**
+     * APIProperty: units
+     * {?}
+     */    
+    units: null,
+
+    /**
+     * APIProperty: resolution
+     * {Float}
+     */
+    resolution: OpenLayers.DOTS_PER_INCH,
+    
+    /**
+     * Constant: DEFAULT_PARAMS
+     * {Object} parameters set by default. The default parameters set 
+     * the format via the 'i' parameter to 'jpeg'.    
+     */
+    DEFAULT_PARAMS: {
+        i: 'jpeg',
+        map: ''
+    },
+        
+    /**
+     * Constructor: OpenLayers.Layer.KaMap
+     * 
+     * Parameters:
+     * name - {String}
+     * url - {String}
+     * params - {Object} Parameters to be sent to the HTTP server in the
+     *    query string for the tile. The format can be set via the 'i'
+     *    parameter (defaults to jpg) , and the map should be set via 
+     *    the 'map' parameter. It has been reported that ka-Map may behave
+     *    inconsistently if your format parameter does not match the format
+     *    parameter configured in your config.php. (See ticket #327 for more
+     *    information.)
+     * options - {Object} Additional options for the layer. Any of the 
+     *     APIProperties listed on this layer, and any layer types it
+     *     extends, can be overridden through the options parameter. 
+     */
+    initialize: function(name, url, params, options) {
+        var newArguments = [];
+        newArguments.push(name, url, params, options);
+        OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+        this.params = OpenLayers.Util.applyDefaults(
+            this.params, this.DEFAULT_PARAMS
+        );
+    },
+
+    /**
+     * Method: getURL
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     * 
+     * Returns:
+     * {String} A string with the layer's url and parameters and also the 
+     *          passed-in bounds and appropriate tile size specified as 
+     *          parameters
+     */
+    getURL: function (bounds) {
+        bounds = this.adjustBounds(bounds);
+        var mapRes = this.map.getResolution();
+        var scale = Math.round((this.map.getScale() * 10000)) / 10000;
+        var pX = Math.round(bounds.left / mapRes);
+        var pY = -Math.round(bounds.top / mapRes);
+        return this.getFullRequestString(
+                      { t: pY, 
+                        l: pX,
+                        s: scale
+                      });
+    },
+
+    /**
+     * Method: addTile
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * position - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {<OpenLayers.Tile.Image>}
+     */    
+    addTile:function(bounds,position) {
+        var url = this.getURL(bounds);
+        return new OpenLayers.Tile.Image(this, position, bounds, 
+                                             url, this.tileSize);
+    },
+
+    /** 
+     * Method: calculateGridLayout
+     * ka-Map uses the center point of the map as an origin for 
+     * its tiles. Override calculateGridLayout to center tiles 
+     * correctly for this case.
+     *
+     * Parameters:
+     * bounds - {<OpenLayers.Bound>}
+     * extent - {<OpenLayers.Bounds>}
+     * resolution - {Number}
+     *
+     * Returns:
+     * Object containing properties tilelon, tilelat, tileoffsetlat,
+     * tileoffsetlat, tileoffsetx, tileoffsety
+     */
+    calculateGridLayout: function(bounds, extent, resolution) {
+        var tilelon = resolution*this.tileSize.w;
+        var tilelat = resolution*this.tileSize.h;
+        
+        var offsetlon = bounds.left;
+        var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
+        var tilecolremain = offsetlon/tilelon - tilecol;
+        var tileoffsetx = -tilecolremain * this.tileSize.w;
+        var tileoffsetlon = tilecol * tilelon;
+        
+        var offsetlat = bounds.top;  
+        var tilerow = Math.ceil(offsetlat/tilelat) + this.buffer;
+        var tilerowremain = tilerow - offsetlat/tilelat;
+        var tileoffsety = -(tilerowremain+1) * this.tileSize.h;
+        var tileoffsetlat = tilerow * tilelat;
+        
+        return { 
+          tilelon: tilelon, tilelat: tilelat,
+          tileoffsetlon: tileoffsetlon, tileoffsetlat: tileoffsetlat,
+          tileoffsetx: tileoffsetx, tileoffsety: tileoffsety
+        };
+    },    
+
+    /**
+     * APIMethod: clone
+     * 
+     * Parameters: 
+     * obj - {Object}
+     * 
+     * Returns:
+     * {<OpenLayers.Layer.Kamap>} An exact clone of this OpenLayers.Layer.KaMap
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.KaMap(this.name,
+                                            this.url,
+                                            this.params,
+                                            this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+        if (this.tileSize != null) {
+            obj.tileSize = this.tileSize.clone();
+        }
+        
+        // we do not want to copy reference to grid, so we make a new array
+        obj.grid = [];
+
+        return obj;
+    },    
+    
+    /**
+     * APIMethod: getTileBounds
+     * Returns The tile bounds for a layer given a pixel location.
+     *
+     * Parameters:
+     * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
+     */
+    getTileBounds: function(viewPortPx) {
+        var resolution = this.getResolution();
+        var tileMapWidth = resolution * this.tileSize.w;
+        var tileMapHeight = resolution * this.tileSize.h;
+        var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
+        var tileLeft = tileMapWidth * Math.floor(mapPoint.lon / tileMapWidth);
+        var tileBottom = tileMapHeight * Math.floor(mapPoint.lat / tileMapHeight);
+        return new OpenLayers.Bounds(tileLeft, tileBottom,
+                                     tileLeft + tileMapWidth,
+                                     tileBottom + tileMapHeight);
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.KaMap"
+});
+/* ======================================================================
     OpenLayers/Layer/MapGuide.js
    ====================================================================== */
 
@@ -14889,7 +35691,7 @@
  * full text of the license. */
 
 /**
- * @requires OpenLayers/Ajax.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
  * @requires OpenLayers/Layer/Grid.js
  */
 
@@ -14914,7 +35716,7 @@
      * {Boolean} use tile server or request single tile image. Note that using
      *    singleTile *and* isBaseLayer false is *not recommend*: it uses synchronous
      *    XMLHttpRequests to load tiles, and this will *lock up users browsers*
-     *    during requests.
+     *    during requests if the server fails to respond.
      **/
     singleTile: false,
     
@@ -14949,18 +35751,29 @@
      * Constructor: OpenLayers.Layer.MapGuide
      * Create a new Mapguide layer, either tiled or untiled.  
      *
-     * For tiled layers, the 'groupName' and 'mapDefnition' options 
-     * must be specified as options.
+     * For tiled layers, the 'groupName' and 'mapDefinition' values 
+     * must be specified as parameters in the constructor.
      *
-     * For untiled layers, specify either combination of 'mapName' and
-     * 'session', or 'mapDefinition' and 'locale'.
+     * For untiled base layers, specify either combination of 'mapName' and
+     * 'session', or 'mapDefinition' and 'locale'.  
      *
+     * For untiled overlay layers (singleTile=true and isBaseLayer=false), 
+     * mapName and session are required parameters for the Layer constructor.  
+     * Also NOTE: untiled overlay layers issues a synchronous AJAX request 
+     * before the image request can be issued so the users browser may lock
+     * up if the MG Web tier does not respond in a timely fashion.
+     *
+     * NOTE: MapGuide OS uses a DPI value and degrees to meters conversion 
+     * factor that are different than the defaults used in OpenLayers, 
+     * so these must be adjusted accordingly in your application.  
+     * See the MapGuide example for how to set these values for MGOS.
+     *
      * Parameters:
      * name - {String} Name of the layer displayed in the interface
      * url - {String} Location of the MapGuide mapagent executable
      *            (e.g. http://localhost:8008/mapguide/mapagent/mapagent.fcgi)
      * params - {Object} hashtable of additional parameters to use. Some
-     *     parameters may require additional code on the serer. The ones that
+     *     parameters may require additional code on the server. The ones that
      *     you may want to use are: 
      *   - mapDefinition - {String} The MapGuide resource definition
      *            (e.g. Library://Samples/Gmap/Maps/gmapTiled.MapDefinition)
@@ -15002,6 +35815,7 @@
                            this.params,
                            this.SINGLE_TILE_PARAMS
                            );
+                           
         } else {
             //initialize for tiled layers
             OpenLayers.Util.applyDefaults(
@@ -15082,18 +35896,15 @@
             
             //but we first need to call GETVISIBLEMAPEXTENT to set the extent
             var getVisParams = {};
+            getVisParams = OpenLayers.Util.extend(getVisParams, params);
             getVisParams.operation = "GETVISIBLEMAPEXTENT";
             getVisParams.version = "1.0.0";
             getVisParams.session = this.params.session;
             getVisParams.mapName = this.params.mapName;
             getVisParams.format = 'text/xml';
-            getVisParams = OpenLayers.Util.extend(getVisParams, params);
-                
-            new OpenLayers.Ajax.Request(this.url, 
-                  { parameters: getVisParams,
-                    method: 'get',
-                    asynchronous: false   //must be synchronous call to return control here
-                  });
+            url = this.getFullRequestString( getVisParams );
+            
+            OpenLayers.Request.GET({url: url, async: false});
           }
           
           //construct the full URL
@@ -15113,7 +35924,7 @@
                            tilerow: rowidx,
                            scaleindex: this.resolutions.length - this.map.zoom - 1
                         });
-         }
+           }
         
         return url;
     },
@@ -15268,12 +36079,9 @@
         newArguments.push(name, url, params, options);
         OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
 
-        if (arguments.length > 0) {
-            OpenLayers.Util.applyDefaults(
-                           this.params,
-                           this.DEFAULT_PARAMS
-                           );
-        }
+        this.params = OpenLayers.Util.applyDefaults(
+            this.params, this.DEFAULT_PARAMS
+        );
 
         // unless explicitly set in options, if the layer is transparent, 
         // it will be an overlay
@@ -15426,6 +36234,334 @@
     CLASS_NAME: "OpenLayers.Layer.MapServer"
 });
 /* ======================================================================
+    OpenLayers/Layer/TMS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * licence.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.TMS
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.TMS = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+    /**
+     * APIProperty: serviceVersion
+     * {String}
+     */
+    serviceVersion: "1.0.0",
+
+    /**
+     * APIProperty: isBaseLayer
+     * {Boolean}
+     */
+    isBaseLayer: true,
+
+    /**
+     * APIProperty: tileOrigin
+     * {<OpenLayers.Pixel>}
+     */
+    tileOrigin: null,
+
+    /**
+     * Constructor: OpenLayers.Layer.TMS
+     * 
+     * Parameters:
+     * name - {String}
+     * url - {String}
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, url, options) {
+        var newArguments = [];
+        newArguments.push(name, url, {}, options);
+        OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+    },    
+
+    /**
+     * APIMethod:destroy
+     */
+    destroy: function() {
+        // for now, nothing special to do here. 
+        OpenLayers.Layer.Grid.prototype.destroy.apply(this, arguments);  
+    },
+
+    
+    /**
+     * APIMethod: clone
+     * 
+     * Parameters:
+     * obj - {Object}
+     * 
+     * Returns:
+     * {<OpenLayers.Layer.TMS>} An exact clone of this <OpenLayers.Layer.TMS>
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.TMS(this.name,
+                                           this.url,
+                                           this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    },    
+    
+    /**
+     * Method: getURL
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * 
+     * Returns:
+     * {String} A string with the layer's url and parameters and also the 
+     *          passed-in bounds and appropriate tile size specified as 
+     *          parameters
+     */
+    getURL: function (bounds) {
+        bounds = this.adjustBounds(bounds);
+        var res = this.map.getResolution();
+        var x = Math.round((bounds.left - this.tileOrigin.lon) / (res * this.tileSize.w));
+        var y = Math.round((bounds.bottom - this.tileOrigin.lat) / (res * this.tileSize.h));
+        var z = this.map.getZoom();
+        var path = this.serviceVersion + "/" + this.layername + "/" + z + "/" + x + "/" + y + "." + this.type; 
+        var url = this.url;
+        if (url instanceof Array) {
+            url = this.selectUrl(path, url);
+        }
+        return url + path;
+    },
+
+    /**
+     * Method: addTile
+     * addTile creates a tile, initializes it, and adds it to the layer div. 
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * position - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {<OpenLayers.Tile.Image>} The added OpenLayers.Tile.Image
+     */
+    addTile:function(bounds,position) {
+        return new OpenLayers.Tile.Image(this, position, bounds, 
+                                         null, this.tileSize);
+    },
+
+    /** 
+     * APIMethod: setMap
+     * When the layer is added to a map, then we can fetch our origin 
+     *    (if we don't have one.) 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    setMap: function(map) {
+        OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+        if (!this.tileOrigin) { 
+            this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left,
+                                                this.map.maxExtent.bottom);
+        }                                       
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.TMS"
+});
+/* ======================================================================
+    OpenLayers/Layer/TileCache.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * licence.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.TileCache
+ * A read only TileCache layer.  Used to requests tiles cached by TileCache in
+ *     a web accessible cache.  This means that you have to pre-populate your
+ *     cache before this layer can be used.  It is meant only to read tiles
+ *     created by TileCache, and not to make calls to TileCache for tile
+ *     creation.  Create a new instance with the
+ *     <OpenLayers.Layer.TileCache> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.TileCache = OpenLayers.Class(OpenLayers.Layer.Grid, {
+
+    /** 
+     * APIProperty: isBaseLayer
+     * {Boolean} Treat this layer as a base layer.  Default is true.
+     */
+    isBaseLayer: true,
+    
+    /**
+     * APIProperty: tileOrigin
+     * {<OpenLayers.LonLat>} Location of the tile lattice origin.  Default is
+     *     bottom left of the maxExtent.
+     */
+    tileOrigin: null,
+
+    /** 
+     * APIProperty: format
+     * {String} Mime type of the images returned.  Default is image/png.
+     */
+    format: 'image/png',
+
+    /**
+     * Constructor: OpenLayers.Layer.TileCache
+     * Create a new read only TileCache layer.
+     *
+     * Parameters:
+     * name - {String} Name of the layer displayed in the interface
+     * url - {String} Location of the web accessible cache (not the location of
+     *     your tilecache script!)
+     * layername - {String} Layer name as defined in the TileCache 
+     *     configuration
+     * options - {Object} Optional object with properties to be set on the
+     *     layer.  Note that you should speficy your resolutions to match
+     *     your TileCache configuration.  This can be done by setting
+     *     the resolutions array directly (here or on the map), by setting
+     *     maxResolution and numZoomLevels, or by using scale based properties.
+     */
+    initialize: function(name, url, layername, options) {
+        this.layername = layername;
+        OpenLayers.Layer.Grid.prototype.initialize.apply(this,
+                                                         [name, url, {}, options]);
+        this.extension = this.format.split('/')[1].toLowerCase();
+        this.extension = (this.extension == 'jpg') ? 'jpeg' : this.extension;
+    },    
+
+    /**
+     * APIMethod: clone
+     * obj - {Object} 
+     * 
+     * Returns:
+     * {<OpenLayers.Layer.TileCache>} An exact clone of this 
+     *     <OpenLayers.Layer.TileCache>
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.TileCache(this.name,
+                                                 this.url,
+                                                 this.layername,
+                                                 this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    },    
+    
+    /**
+     * Method: getURL
+     *
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     * 
+     * Returns:
+     * {String} A string with the layer's url and parameters and also the 
+     *     passed-in bounds and appropriate tile size specified as parameters.
+     */
+    getURL: function(bounds) {
+        var res = this.map.getResolution();
+        var bbox = this.maxExtent;
+        var size = this.tileSize;
+        var tileX = Math.round((bounds.left - bbox.left) / (res * size.w));
+        var tileY = Math.round((bounds.bottom - bbox.bottom) / (res * size.h));
+        var tileZ = this.map.zoom;
+        /**
+         * Zero-pad a positive integer.
+         * number - {Int} 
+         * length - {Int} 
+         *
+         * Returns:
+         * {String} A zero-padded string
+         */
+        function zeroPad(number, length) {
+            number = String(number);
+            var zeros = [];
+            for(var i=0; i<length; ++i) {
+                zeros.push('0');
+            }
+            return zeros.join('').substring(0, length - number.length) + number;
+        }
+        var components = [
+            this.layername,
+            zeroPad(tileZ, 2),
+            zeroPad(parseInt(tileX / 1000000), 3),
+            zeroPad((parseInt(tileX / 1000) % 1000), 3),
+            zeroPad((parseInt(tileX) % 1000), 3),
+            zeroPad(parseInt(tileY / 1000000), 3),
+            zeroPad((parseInt(tileY / 1000) % 1000), 3),
+            zeroPad((parseInt(tileY) % 1000), 3) + '.' + this.extension
+        ];
+        var path = components.join('/'); 
+        var url = this.url;
+        if (url instanceof Array) {
+            url = this.selectUrl(path, url);
+        }
+        url = (url.charAt(url.length - 1) == '/') ? url : url + '/';
+        return url + path;
+    },
+
+    /**
+     * Method: addTile
+     * Create a tile, initialize it, and add it to the layer div. 
+     *
+     * Parameters: 
+     * bounds - {<OpenLayers.Bounds>} 
+     * position - {<OpenLayers.Pixel>}
+     *
+     * Returns:
+     * {<OpenLayers.Tile.Image>} The added <OpenLayers.Tile.Image>
+     */
+    addTile:function(bounds, position) {
+        var url = this.getURL(bounds);
+        return new OpenLayers.Tile.Image(this, position, bounds, 
+                                             url, this.tileSize);
+    },
+
+    /** 
+     * Method: setMap
+     * When the layer is added to a map, then we can fetch our origin 
+     *     (if we don't have one.) 
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments);
+        if (!this.tileOrigin) { 
+            this.tileOrigin = new OpenLayers.LonLat(this.map.maxExtent.left,
+                                                    this.map.maxExtent.bottom);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.TileCache"
+});
+/* ======================================================================
     OpenLayers/Layer/WMS.js
    ====================================================================== */
 
@@ -15617,7 +36753,8 @@
      * Catch changeParams and uppercase the new params to be merged in
      *     before calling changeParams on the super class.
      * 
-     *     Once params have been changed, we will need to re-init our tiles.
+     *     Once params have been changed, the tiles will be reloaded with
+     *     the new parameters.
      * 
      * Parameters:
      * newParams - {Object} Hashtable of new params to use
@@ -15630,7 +36767,7 @@
     },
 
     /** 
-     * Method: getFullRequestString
+     * APIMethod: getFullRequestString
      * Combine the layer's url with its params and these newParams. 
      *   
      *     Add the SRS parameter from projection -- this is probably
@@ -15654,3 +36791,10969 @@
 
     CLASS_NAME: "OpenLayers.Layer.WMS"
 });
+/* ======================================================================
+    OpenLayers/Layer/WorldWind.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Layer/Grid.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WorldWind
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.Grid>
+ */
+OpenLayers.Layer.WorldWind = OpenLayers.Class(OpenLayers.Layer.Grid, {
+    
+    DEFAULT_PARAMS: {
+    },
+
+    /**
+     * APIProperty: isBaseLayer
+     * WorldWind layer is a base layer by default.
+     */
+    isBaseLayer: true,    
+
+    
+    /** 
+     * APIProperty: lzd
+     * LevelZeroTileSizeDegrees
+     */
+    lzd: null,
+
+    /**
+     * APIProperty: zoomLevels
+     * Number of zoom levels.
+     */
+    zoomLevels: null,
+    
+    /**
+     * Constructor: OpenLayers.Layer.WorldWind
+     * 
+     * Parameters:
+     * name - {String} Name of Layer
+     * url - {String} Base URL  
+     * lzd - {Float} Level zero tile size degrees 
+     * zoomLevels - {Int} number of zoom levels
+     * params - {Object} additional parameters
+     * options - {Object} additional options
+     */
+    initialize: function(name, url, lzd, zoomLevels, params, options) {
+        this.lzd = lzd;
+        this.zoomLevels = zoomLevels;
+        var newArguments = [];
+        newArguments.push(name, url, params, options);
+        OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments);
+        this.params = OpenLayers.Util.applyDefaults(
+            this.params, this.DEFAULT_PARAMS
+        );
+    },
+    /**
+     * Method: addTile
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     * position - {<OpenLayers.Pixel>}
+     * 
+     * Returns:
+     * {<OpenLayers.Tile.Image>} The added OpenLayers.Tile.Image
+     */
+    addTile:function(bounds,position) {
+        return new OpenLayers.Tile.Image(this, position, bounds, 
+                                             null, this.tileSize);
+    },
+
+    /**
+     * Method: getZoom
+     * Convert map zoom to WW zoom.
+     */
+    getZoom: function () {
+        var zoom = this.map.getZoom();
+        var extent = this.map.getMaxExtent();
+        zoom = zoom - Math.log(this.maxResolution / (this.lzd/512))/Math.log(2);
+        return zoom;
+    },
+
+    /**
+     * Method: getURL
+     *
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     *
+     * Returns:
+     * {String} A string with the layer's url and parameters and also the 
+     *           passed-in bounds and appropriate tile size specified as 
+     *           parameters
+     */
+    getURL: function (bounds) {
+        bounds = this.adjustBounds(bounds);
+        var zoom = this.getZoom();
+        var extent = this.map.getMaxExtent();
+        var deg = this.lzd/Math.pow(2,this.getZoom());
+        var x = Math.floor((bounds.left - extent.left)/deg);
+        var y = Math.floor((bounds.bottom - extent.bottom)/deg);
+        if (this.map.getResolution() <= (this.lzd/512)
+            && this.getZoom() <= this.zoomLevels) {
+            return this.getFullRequestString(
+              { L: zoom, 
+                X: x,
+                Y: y
+              });
+        } else {
+            return OpenLayers.Util.getImagesLocation() + "blank.gif";
+        }
+
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.WorldWind"
+});
+/* ======================================================================
+    OpenLayers/Rule.js
+   ====================================================================== */
+
+/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Style.js
+ */
+
+/**
+ * Class: OpenLayers.Rule
+ * This class represents an SLD Rule, as being used for rule-based SLD styling.
+ */
+OpenLayers.Rule = OpenLayers.Class({
+    
+    /**
+     * Property: id
+     * {String} A unique id for this session.
+     */
+    id: null,
+    
+    /**
+     * APIProperty: name
+     * {String} name of this rule
+     */
+    name: 'default',
+    
+    /**
+     * Property: title
+     * {String} Title of this rule (set if included in SLD)
+     */
+    title: null,
+    
+    /**
+     * Property: description
+     * {String} Description of this rule (set if abstract is included in SLD)
+     */
+    description: null,
+
+    /**
+     * Property: context
+     * {Object} An optional object with properties that the rule should be
+     * evaluated against. If no context is specified, feature.attributes will
+     * be used.
+     */
+    context: null,
+    
+    /**
+     * Property: filter
+     * {<OpenLayers.Filter>} Optional filter for the rule.
+     */
+    filter: null,
+
+    /**
+     * Property: elseFilter
+     * {Boolean} Determines whether this rule is only to be applied only if
+     * no other rules match (ElseFilter according to the SLD specification). 
+     * Default is false.  For instances of OpenLayers.Rule, if elseFilter is
+     * false, the rule will always apply.  For subclasses, the else property is 
+     * ignored.
+     */
+    elseFilter: false,
+    
+    /**
+     * Property: symbolizer
+     * {Object} Symbolizer or hash of symbolizers for this rule. If hash of
+     * symbolizers, keys are one or more of ["Point", "Line", "Polygon"]. The
+     * latter if useful if it is required to style e.g. vertices of a line
+     * with a point symbolizer. Note, however, that this is not implemented
+     * yet in OpenLayers, but it is the way how symbolizers are defined in
+     * SLD.
+     */
+    symbolizer: null,
+    
+    /**
+     * APIProperty: minScaleDenominator
+     * {Number} or {String} minimum scale at which to draw the feature.
+     * In the case of a String, this can be a combination of text and
+     * propertyNames in the form "literal ${propertyName}"
+     */
+    minScaleDenominator: null,
+
+    /**
+     * APIProperty: maxScaleDenominator
+     * {Number} or {String} maximum scale at which to draw the feature.
+     * In the case of a String, this can be a combination of text and
+     * propertyNames in the form "literal ${propertyName}"
+     */
+    maxScaleDenominator: null,
+    
+    /** 
+     * Constructor: OpenLayers.Rule
+     * Creates a Rule.
+     *
+     * Parameters:
+     * options - {Object} An optional object with properties to set on the
+     *           rule
+     * 
+     * Returns:
+     * {<OpenLayers.Rule>}
+     */
+    initialize: function(options) {
+        this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
+        this.symbolizer = {};
+
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /** 
+     * APIMethod: destroy
+     * nullify references to prevent circular references and memory leaks
+     */
+    destroy: function() {
+        for (var i in this.symbolizer) {
+            this.symbolizer[i] = null;
+        }
+        this.symbolizer = null;
+    },
+    
+    /**
+     * APIMethod: evaluate
+     * evaluates this rule for a specific feature
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature>} feature to apply the rule to.
+     * 
+     * Returns:
+     * {Boolean} true if the rule applies, false if it does not.
+     * This rule is the default rule and always returns true.
+     */
+    evaluate: function(feature) {
+        var context = this.getContext(feature);
+        var applies = true;
+
+        if (this.minScaleDenominator || this.maxScaleDenominator) {
+            var scale = feature.layer.map.getScale();
+        }
+        
+        // check if within minScale/maxScale bounds
+        if (this.minScaleDenominator) {
+            applies = scale >= OpenLayers.Style.createLiteral(
+                    this.minScaleDenominator, context);
+        }
+        if (applies && this.maxScaleDenominator) {
+            applies = scale < OpenLayers.Style.createLiteral(
+                    this.maxScaleDenominator, context);
+        }
+        
+        // check if optional filter applies
+        if(applies && this.filter) {
+            // feature id filters get the feature, others get the context
+            if(this.filter.CLASS_NAME == "OpenLayers.Filter.FeatureId") {
+                applies = this.filter.evaluate(feature);
+            } else {
+                applies = this.filter.evaluate(context);
+            }
+        }
+
+        return applies;
+    },
+    
+    /**
+     * Method: getContext
+     * Gets the context for evaluating this rule
+     * 
+     * Paramters:
+     * feature - {<OpenLayers.Feature>} feature to take the context from if
+     *           none is specified.
+     */
+    getContext: function(feature) {
+        var context = this.context;
+        if (!context) {
+            context = feature.attributes || feature.data;
+        }
+        if (typeof this.context == "function") {
+            context = this.context(feature);
+        }
+        return context;
+    },
+        
+    CLASS_NAME: "OpenLayers.Rule"
+});
+/* ======================================================================
+    OpenLayers/StyleMap.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Style.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+ 
+/**
+ * Class: OpenLayers.StyleMap
+ */
+OpenLayers.StyleMap = OpenLayers.Class({
+    
+    /**
+     * Property: styles
+     * Hash of {<OpenLayers.Style>}, keyed by names of well known
+     * rendering intents (e.g. "default", "temporary", "select").
+     */
+    styles: null,
+    
+    /**
+     * Property: extendDefault
+     * {Boolean} if true, every render intent will extend the symbolizers
+     * specified for the "default" intent at rendering time. Otherwise, every
+     * rendering intent will be treated as a completely independent style.
+     */
+    extendDefault: true,
+    
+    /**
+     * Constructor: OpenLayers.StyleMap
+     * 
+     * Parameters:
+     * style   - {Object} Optional. Either a style hash, or a style object, or
+     *           a hash of style objects (style hashes) keyed by rendering
+     *           intent. If just one style hash or style object is passed,
+     *           this will be used for all known render intents (default,
+     *           select, temporary)
+     * options - {Object} optional hash of additional options for this
+     *           instance
+     */
+    initialize: function (style, options) {
+        this.styles = {
+            "default": new OpenLayers.Style(
+                OpenLayers.Feature.Vector.style["default"]),
+            "select": new OpenLayers.Style(
+                OpenLayers.Feature.Vector.style["select"]),
+            "temporary": new OpenLayers.Style(
+                OpenLayers.Feature.Vector.style["temporary"])
+        };
+        
+        // take whatever the user passed as style parameter and convert it
+        // into parts of stylemap.
+        if(style instanceof OpenLayers.Style) {
+            // user passed a style object
+            this.styles["default"] = style;
+            this.styles["select"] = style;
+            this.styles["temporary"] = style;
+        } else if(typeof style == "object") {
+            for(var key in style) {
+                if(style[key] instanceof OpenLayers.Style) {
+                    // user passed a hash of style objects
+                    this.styles[key] = style[key];
+                } else if(typeof style[key] == "object") {
+                    // user passsed a hash of style hashes
+                    this.styles[key] = new OpenLayers.Style(style[key]);
+                } else {
+                    // user passed a style hash (i.e. symbolizer)
+                    this.styles["default"] = new OpenLayers.Style(style);
+                    this.styles["select"] = new OpenLayers.Style(style);
+                    this.styles["temporary"] = new OpenLayers.Style(style);
+                    break;
+                }
+            }
+        }
+        OpenLayers.Util.extend(this, options);
+    },
+
+    /**
+     * Method: destroy
+     */
+    destroy: function() {
+        for(var key in this.styles) {
+            this.styles[key].destroy();
+        }
+        this.styles = null;
+    },
+    
+    /**
+     * Method: createSymbolizer
+     * Creates the symbolizer for a feature for a render intent.
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature>} The feature to evaluate the rules
+     *           of the intended style against.
+     * intent  - {String} The intent determines the symbolizer that will be
+     *           used to draw the feature. Well known intents are "default"
+     *           (for just drawing the features), "select" (for selected
+     *           features) and "temporary" (for drawing features).
+     * 
+     * Returns:
+     * {Object} symbolizer hash
+     */
+    createSymbolizer: function(feature, intent) {
+        if(!feature) {
+            feature = new OpenLayers.Feature.Vector();
+        }
+        if(!this.styles[intent]) {
+            intent = "default";
+        }
+        feature.renderIntent = intent;
+        var defaultSymbolizer = {};
+        if(this.extendDefault && intent != "default") {
+            defaultSymbolizer = this.styles["default"].createSymbolizer(feature);
+        }
+        return OpenLayers.Util.extend(defaultSymbolizer,
+            this.styles[intent].createSymbolizer(feature));
+    },
+    
+    /**
+     * Method: addUniqueValueRules
+     * Convenience method to create comparison rules for unique values of a
+     * property. The rules will be added to the style object for a specified
+     * rendering intent. This method is a shortcut for creating something like
+     * the "unique value legends" familiar from well known desktop GIS systems
+     * 
+     * Parameters:
+     * renderIntent - {String} rendering intent to add the rules to
+     * property     - {String} values of feature attributes to create the
+     *                rules for
+     * symbolizers  - {Object} Hash of symbolizers, keyed by the desired
+     *                property values 
+     * context      - {Object} An optional object with properties that
+     *                symbolizers' property values should be evaluated
+     *                against. If no context is specified, feature.attributes
+     *                will be used
+     */
+    addUniqueValueRules: function(renderIntent, property, symbolizers, context) {
+        var rules = [];
+        for (var value in symbolizers) {
+            rules.push(new OpenLayers.Rule({
+                symbolizer: symbolizers[value],
+                context: context,
+                filter: new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.EQUAL_TO,
+                    property: property,
+                    value: value
+                })
+            }));
+        }
+        this.styles[renderIntent].addRules(rules);
+    },
+
+    CLASS_NAME: "OpenLayers.StyleMap"
+});
+/* ======================================================================
+    OpenLayers/Control/NavToolbar.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/Navigation.js
+ * @requires OpenLayers/Control/ZoomBox.js
+ */
+
+/**
+ * Class: OpenLayers.Control.NavToolbar
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Control.Panel>
+ */
+OpenLayers.Control.NavToolbar = OpenLayers.Class(OpenLayers.Control.Panel, {
+
+    /**
+     * Constructor: OpenLayers.Control.NavToolbar 
+     * Add our two mousedefaults controls.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be used
+     *     to extend the control.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+        this.addControls([
+          new OpenLayers.Control.Navigation(),
+          new OpenLayers.Control.ZoomBox()
+        ]);
+    },
+
+    /**
+     * Method: draw 
+     * calls the default draw, and then activates mouse defaults.
+     */
+    draw: function() {
+        var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments);
+        this.activateControl(this.controls[0]);
+        return div;
+    },
+
+    CLASS_NAME: "OpenLayers.Control.NavToolbar"
+});
+/* ======================================================================
+    OpenLayers/Filter/Comparison.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+  * full text of the license. */
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Comparison
+ * This class represents a comparison filter.
+ * 
+ * Inherits from
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Comparison = OpenLayers.Class(OpenLayers.Filter, {
+
+    /**
+     * APIProperty: type
+     * {String} type: type of the comparison. This is one of
+     * - OpenLayers.Filter.Comparison.EQUAL_TO                 = "==";
+     * - OpenLayers.Filter.Comparison.NOT_EQUAL_TO             = "!=";
+     * - OpenLayers.Filter.Comparison.LESS_THAN                = "<";
+     * - OpenLayers.Filter.Comparison.GREATER_THAN             = ">";
+     * - OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO    = "<=";
+     * - OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+     * - OpenLayers.Filter.Comparison.BETWEEN                  = "..";
+     * - OpenLayers.Filter.Comparison.LIKE                     = "~"; 
+     */
+    type: null,
+    
+    /**
+     * APIProperty: property
+     * {String}
+     * name of the context property to compare
+     */
+    property: null,
+    
+    /**
+     * APIProperty: value
+     * {Number} or {String}
+     * comparison value for binary comparisons. In the case of a String, this
+     * can be a combination of text and propertyNames in the form
+     * "literal ${propertyName}"
+     */
+    value: null,
+    
+    /**
+     * APIProperty: lowerBoundary
+     * {Number} or {String}
+     * lower boundary for between comparisons. In the case of a String, this
+     * can be a combination of text and propertyNames in the form
+     * "literal ${propertyName}"
+     */
+    lowerBoundary: null,
+    
+    /**
+     * APIProperty: upperBoundary
+     * {Number} or {String}
+     * upper boundary for between comparisons. In the case of a String, this
+     * can be a combination of text and propertyNames in the form
+     * "literal ${propertyName}"
+     */
+    upperBoundary: null,
+
+    /** 
+     * Constructor: OpenLayers.Filter.Comparison
+     * Creates a comparison rule.
+     *
+     * Parameters:
+     * options - {Object} An optional object with properties to set on the
+     *           rule
+     * 
+     * Returns:
+     * {<OpenLayers.Filter.Comparison>}
+     */
+    initialize: function(options) {
+        OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: evaluate
+     * Evaluates this filter in a specific context.  Should be implemented by
+     *     subclasses.
+     * 
+     * Parameters:
+     * context - {Object} Context to use in evaluating the filter.
+     * 
+     * Returns:
+     * {Boolean} The filter applies.
+     */
+    evaluate: function(context) {
+        switch(this.type) {
+            case OpenLayers.Filter.Comparison.EQUAL_TO:
+            case OpenLayers.Filter.Comparison.LESS_THAN:
+            case OpenLayers.Filter.Comparison.GREATER_THAN:
+            case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
+            case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
+                return this.binaryCompare(context, this.property, this.value);
+            
+            case OpenLayers.Filter.Comparison.BETWEEN:
+                var result =
+                        context[this.property] >= this.lowerBoundary;
+                result = result &&
+                        context[this.property] <= this.upperBoundary;
+                return result;
+            case OpenLayers.Filter.Comparison.LIKE:
+                var regexp = new RegExp(this.value,
+                                "gi");
+                return regexp.test(context[this.property]); 
+        }
+    },
+    
+    /**
+     * APIMethod: value2regex
+     * Converts the value of this rule into a regular expression string,
+     * according to the wildcard characters specified. This method has to
+     * be called after instantiation of this class, if the value is not a
+     * regular expression already.
+     * 
+     * Parameters:
+     * wildCard   - {<Char>} wildcard character in the above value, default
+     *              is "*"
+     * singleChar - {<Char>) single-character wildcard in the above value
+     *              default is "."
+     * escape     - {<Char>) escape character in the above value, default is
+     *              "!"
+     * 
+     * Returns:
+     * {String} regular expression string
+     */
+    value2regex: function(wildCard, singleChar, escapeChar) {
+        if (wildCard == ".") {
+            var msg = "'.' is an unsupported wildCard character for "+
+                    "OpenLayers.Filter.Comparison";
+            OpenLayers.Console.error(msg);
+            return null;
+        }
+        
+
+        // set UMN MapServer defaults for unspecified parameters
+        wildCard = wildCard ? wildCard : "*";
+        singleChar = singleChar ? singleChar : ".";
+        escapeChar = escapeChar ? escapeChar : "!";
+        
+        this.value = this.value.replace(
+                new RegExp("\\"+escapeChar+"(.|$)", "g"), "\\$1")
+        this.value = this.value.replace(
+                new RegExp("\\"+singleChar, "g"), ".");
+        this.value = this.value.replace(
+                new RegExp("\\"+wildCard, "g"), ".*");
+        this.value = this.value.replace(
+                new RegExp("\\\\.\\*", "g"), "\\"+wildCard);
+        this.value = this.value.replace(
+                new RegExp("\\\\\\.", "g"), "\\"+singleChar);
+        
+        return this.value;
+    },
+    
+    /**
+     * Method: regex2value
+     * Convert the value of this rule from a regular expression string into an
+     *     ogc literal string using a wildCard of *, a singleChar of ., and an
+     *     escape of !.  Leaves the <value> property unmodified.
+     * 
+     * Returns:
+     * {String} A string value.
+     */
+    regex2value: function() {
+        
+        var value = this.value;
+        
+        // replace ! with !!
+        value = value.replace(/!/g, "!!");
+
+        // replace \. with !. (watching out for \\.)
+        value = value.replace(/(\\)?\\\./g, function($0, $1) {
+            return $1 ? $0 : "!.";
+        });
+        
+        // replace \* with #* (watching out for \\*)
+        value = value.replace(/(\\)?\\\*/g, function($0, $1) {
+            return $1 ? $0 : "!*";
+        });
+        
+        // replace \\ with \
+        value = value.replace(/\\\\/g, "\\");
+
+        // convert .* to * (the sequence #.* is not allowed)
+        value = value.replace(/\.\*/g, "*");
+        
+        return value;
+    },
+
+    /**
+     * Function: binaryCompare
+     * Compares a feature property to a rule value
+     * 
+     * Parameters:
+     * context  - {Object}
+     * property - {String} or {Number}
+     * value    - {String} or {Number}, same as property
+     * 
+     * Returns:
+     * {Boolean}
+     */
+    binaryCompare: function(context, property, value) {
+        switch (this.type) {
+            case OpenLayers.Filter.Comparison.EQUAL_TO:
+                return context[property] == value;
+            case OpenLayers.Filter.Comparison.NOT_EQUAL_TO:
+                return context[property] != value;
+            case OpenLayers.Filter.Comparison.LESS_THAN:
+                return context[property] < value;
+            case OpenLayers.Filter.Comparison.GREATER_THAN:
+                return context[property] > value;
+            case OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO:
+                return context[property] <= value;
+            case OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO:
+                return context[property] >= value;
+        }      
+    },
+    
+    CLASS_NAME: "OpenLayers.Filter.Comparison"
+});
+
+
+OpenLayers.Filter.Comparison.EQUAL_TO                 = "==";
+OpenLayers.Filter.Comparison.NOT_EQUAL_TO             = "!=";
+OpenLayers.Filter.Comparison.LESS_THAN                = "<";
+OpenLayers.Filter.Comparison.GREATER_THAN             = ">";
+OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO    = "<=";
+OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO = ">=";
+OpenLayers.Filter.Comparison.BETWEEN                  = "..";
+OpenLayers.Filter.Comparison.LIKE                     = "~";
+/* ======================================================================
+    OpenLayers/Filter/FeatureId.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+  * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.FeatureId
+ * This class represents a ogc:FeatureId Filter, as being used for rule-based SLD
+ * styling
+ * 
+ * Inherits from
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.FeatureId = OpenLayers.Class(OpenLayers.Filter, {
+
+    /** 
+     * APIProperty: fids
+     * {Array(String)} Feature Ids to evaluate this rule against. To be passed
+     * To be passed inside the params object.
+     */
+    fids: null,
+    
+    /** 
+     * Constructor: OpenLayers.Filter.FeatureId
+     * Creates an ogc:FeatureId rule.
+     *
+     * Parameters:
+     * options - {Object} An optional object with properties to set on the
+     *           rule
+     * 
+     * Returns:
+     * {<OpenLayers.Filter.FeatureId>}
+     */
+    initialize: function(options) {
+        this.fids = [];
+        OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: evaluate
+     * evaluates this rule for a specific feature
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature>} feature to apply the rule to.
+     *           For vector features, the check is run against the fid,
+     *           for plain features against the id.
+     * 
+     * Returns:
+     * {Boolean} true if the rule applies, false if it does not
+     */
+    evaluate: function(feature) {
+        for (var i=0, len=this.fids.length; i<len; i++) {
+            var fid = feature.fid || feature.id;
+            if (fid == this.fids[i]) {
+                return true;
+            }
+        }
+        return false;
+    },
+    
+    CLASS_NAME: "OpenLayers.Filter.FeatureId"
+});
+/* ======================================================================
+    OpenLayers/Filter/Logical.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+  * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Filter.Logical
+ * This class represents ogc:And, ogc:Or and ogc:Not rules.
+ * 
+ * Inherits from
+ * - <OpenLayers.Filter>
+ */
+OpenLayers.Filter.Logical = OpenLayers.Class(OpenLayers.Filter, {
+
+    /**
+     * APIProperty: filters
+     * {Array(<OpenLayers.Filter>)} Child filters for this filter.
+     */
+    filters: null, 
+     
+    /**
+     * APIProperty: type
+     * {String} type of logical operator. Available types are:
+     * - OpenLayers.Filter.Locical.AND = "&&";
+     * - OpenLayers.Filter.Logical.OR  = "||";
+     * - OpenLayers.Filter.Logical.NOT = "!";
+     */
+    type: null,
+
+    /** 
+     * Constructor: OpenLayers.Filter.Logical
+     * Creates a logical filter (And, Or, Not).
+     *
+     * Parameters:
+     * options - {Object} An optional object with properties to set on the
+     *     filter.
+     * 
+     * Returns:
+     * {<OpenLayers.Filter.Logical>}
+     */
+    initialize: function(options) {
+        this.filters = [];
+        OpenLayers.Filter.prototype.initialize.apply(this, [options]);
+    },
+    
+    /** 
+     * APIMethod: destroy
+     * Remove reference to child filters.
+     */
+    destroy: function() {
+        this.filters = null;
+        OpenLayers.Filter.prototype.destroy.apply(this);
+    },
+
+    /**
+     * APIMethod: evaluate
+     * Evaluates this filter in a specific context.  Should be implemented by
+     *     subclasses.
+     * 
+     * Parameters:
+     * context - {Object} Context to use in evaluating the filter.
+     * 
+     * Returns:
+     * {Boolean} The filter applies.
+     */
+    evaluate: function(context) {
+        switch(this.type) {
+            case OpenLayers.Filter.Logical.AND:
+                for (var i=0, len=this.filters.length; i<len; i++) {
+                    if (this.filters[i].evaluate(context) == false) {
+                        return false;
+                    }
+                }
+                return true;
+                
+            case OpenLayers.Filter.Logical.OR:
+                for (var i=0, len=this.filters.length; i<len; i++) {
+                    if (this.filters[i].evaluate(context) == true) {
+                        return true;
+                    }
+                }
+                return false;
+            
+            case OpenLayers.Filter.Logical.NOT:
+                return (!this.filters[0].evaluate(context));
+        }
+    },
+    
+    CLASS_NAME: "OpenLayers.Filter.Logical"
+});
+
+
+OpenLayers.Filter.Logical.AND = "&&";
+OpenLayers.Filter.Logical.OR  = "||";
+OpenLayers.Filter.Logical.NOT = "!";
+/* ======================================================================
+    OpenLayers/Geometry/Collection.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Collection
+ * A Collection is exactly what it sounds like: A collection of different 
+ * Geometries. These are stored in the local parameter <components> (which
+ * can be passed as a parameter to the constructor). 
+ * 
+ * As new geometries are added to the collection, they are NOT cloned. 
+ * When removing geometries, they need to be specified by reference (ie you 
+ * have to pass in the *exact* geometry to be removed).
+ * 
+ * The <getArea> and <getLength> functions here merely iterate through
+ * the components, summing their respective areas and lengths.
+ *
+ * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
+ *
+ * Inerhits from:
+ *  - <OpenLayers.Geometry> 
+ */
+OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
+
+    /**
+     * APIProperty: components
+     * {Array(<OpenLayers.Geometry>)} The component parts of this geometry
+     */
+    components: null,
+    
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of
+     * components that the collection can include.  A null value means the
+     * component types are not restricted.
+     */
+    componentTypes: null,
+
+    /**
+     * Constructor: OpenLayers.Geometry.Collection
+     * Creates a Geometry Collection -- a list of geoms.
+     *
+     * Parameters: 
+     * components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
+     *
+     */
+    initialize: function (components) {
+        OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+        this.components = [];
+        if (components != null) {
+            this.addComponents(components);
+        }
+    },
+
+    /**
+     * APIMethod: destroy
+     * Destroy this geometry.
+     */
+    destroy: function () {
+        this.components.length = 0;
+        this.components = null;
+    },
+
+    /**
+     * APIMethod: clone
+     * Clone this geometry.
+     *
+     * Returns:
+     * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
+     */
+    clone: function() {
+        var geometry = eval("new " + this.CLASS_NAME + "()");
+        for(var i=0, len=this.components.length; i<len; i++) {
+            geometry.addComponent(this.components[i].clone());
+        }
+        
+        // catch any randomly tagged-on properties
+        OpenLayers.Util.applyDefaults(geometry, this);
+        
+        return geometry;
+    },
+
+    /**
+     * Method: getComponentsString
+     * Get a string representing the components for this collection
+     * 
+     * Returns:
+     * {String} A string representation of the components of this geometry
+     */
+    getComponentsString: function(){
+        var strings = [];
+        for(var i=0, len=this.components.length; i<len; i++) {
+            strings.push(this.components[i].toShortString()); 
+        }
+        return strings.join(",");
+    },
+
+    /**
+     * APIMethod: calculateBounds
+     * Recalculate the bounds by iterating through the components and 
+     * calling calling extendBounds() on each item.
+     */
+    calculateBounds: function() {
+        this.bounds = null;
+        if ( this.components && this.components.length > 0) {
+            this.setBounds(this.components[0].getBounds());
+            for (var i=1, len=this.components.length; i<len; i++) {
+                this.extendBounds(this.components[i].getBounds());
+            }
+        }
+    },
+
+    /**
+     * APIMethod: addComponents
+     * Add components to this geometry.
+     *
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
+     */
+    addComponents: function(components){
+        if(!(components instanceof Array)) {
+            components = [components];
+        }
+        for(var i=0, len=components.length; i<len; i++) {
+            this.addComponent(components[i]);
+        }
+    },
+
+    /**
+     * Method: addComponent
+     * Add a new component (geometry) to the collection.  If this.componentTypes
+     * is set, then the component class name must be in the componentTypes array.
+     *
+     * The bounds cache is reset.
+     * 
+     * Parameters:
+     * component - {<OpenLayers.Geometry>} A geometry to add
+     * index - {int} Optional index into the array to insert the component
+     *
+     * Returns:
+     * {Boolean} The component geometry was successfully added
+     */    
+    addComponent: function(component, index) {
+        var added = false;
+        if(component) {
+            if(this.componentTypes == null ||
+               (OpenLayers.Util.indexOf(this.componentTypes,
+                                        component.CLASS_NAME) > -1)) {
+
+                if(index != null && (index < this.components.length)) {
+                    var components1 = this.components.slice(0, index);
+                    var components2 = this.components.slice(index, 
+                                                           this.components.length);
+                    components1.push(component);
+                    this.components = components1.concat(components2);
+                } else {
+                    this.components.push(component);
+                }
+                component.parent = this;
+                this.clearBounds();
+                added = true;
+            }
+        }
+        return added;
+    },
+    
+    /**
+     * APIMethod: removeComponents
+     * Remove components from this geometry.
+     *
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry>)} The components to be removed
+     */
+    removeComponents: function(components) {
+        if(!(components instanceof Array)) {
+            components = [components];
+        }
+        for(var i=components.length-1; i>=0; --i) {
+            this.removeComponent(components[i]);
+        }
+    },
+    
+    /**
+     * Method: removeComponent
+     * Remove a component from this geometry.
+     *
+     * Parameters:
+     * component - {<OpenLayers.Geometry>} 
+     */
+    removeComponent: function(component) {
+        
+        OpenLayers.Util.removeItem(this.components, component);
+        
+        // clearBounds() so that it gets recalculated on the next call
+        // to this.getBounds();
+        this.clearBounds();
+    },
+
+    /**
+     * APIMethod: getLength
+     * Calculate the length of this geometry
+     *
+     * Returns:
+     * {Float} The length of the geometry
+     */
+    getLength: function() {
+        var length = 0.0;
+        for (var i=0, len=this.components.length; i<len; i++) {
+            length += this.components[i].getLength();
+        }
+        return length;
+    },
+    
+    /**
+     * APIMethod: getArea
+     * Calculate the area of this geometry. Note how this function is overridden
+     * in <OpenLayers.Geometry.Polygon>.
+     *
+     * Returns:
+     * {Float} The area of the collection by summing its parts
+     */
+    getArea: function() {
+        var area = 0.0;
+        for (var i=0, len=this.components.length; i<len; i++) {
+            area += this.components[i].getArea();
+        }
+        return area;
+    },
+
+    /**
+     * APIMethod: move
+     * Moves a collection in place
+     *
+     * Parameters:
+     * x - {Float} The x-displacement (in map units)
+     * y - {Float} The y-displacement (in map units)
+     */
+    move: function(x, y) {
+        for(var i=0, len=this.components.length; i<len; i++) {
+            this.components[i].move(x, y);
+        }
+    },
+
+    /**
+     * APIMethod: rotate
+     * Rotate a geometry around some origin
+     *
+     * Parameters:
+     * angle - {Float} Rotation angle in degrees (measured counterclockwise
+     *                 from the positive x-axis)
+     * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+     */
+    rotate: function(angle, origin) {
+        for(var i=0, len=this.components.length; i<len; ++i) {
+            this.components[i].rotate(angle, origin);
+        }
+    },
+
+    /**
+     * APIMethod: resize
+     * Resize a geometry relative to some origin.  Use this method to apply
+     *     a uniform scaling to a geometry.
+     *
+     * Parameters:
+     * scale - {Float} Factor by which to scale the geometry.  A scale of 2
+     *                 doubles the size of the geometry in each dimension
+     *                 (lines, for example, will be twice as long, and polygons
+     *                 will have four times the area).
+     * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+     * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
+     */
+    resize: function(scale, origin, ratio) {
+        for(var i=0; i<this.components.length; ++i) {
+            this.components[i].resize(scale, origin, ratio);
+        }
+    },
+
+    /**
+     * APIMethod: equals
+     * Tests for equivalent geometries
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     *
+     * Returns:
+     * {Boolean} The coordinates are equivalent
+     */
+    equals: function(geometry) {
+        var equivalent = true;
+        if(!geometry || !geometry.CLASS_NAME ||
+           (this.CLASS_NAME != geometry.CLASS_NAME)) {
+            equivalent = false;
+        } else if(!(geometry.components instanceof Array) ||
+                  (geometry.components.length != this.components.length)) {
+            equivalent = false;
+        } else {
+            for(var i=0, len=this.components.length; i<len; ++i) {
+                if(!this.components[i].equals(geometry.components[i])) {
+                    equivalent = false;
+                    break;
+                }
+            }
+        }
+        return equivalent;
+    },
+
+    /**
+     * APIMethod: transform
+     * Reproject the components geometry from source to dest.
+     * 
+     * Parameters:
+     * source - {<OpenLayers.Projection>} 
+     * dest - {<OpenLayers.Projection>}
+     * 
+     * Returns:
+     * {<OpenLayers.Geometry>} 
+     */
+    transform: function(source, dest) {
+        if (source && dest) {
+            for (var i=0, len=this.components.length; i<len; i++) {  
+                var component = this.components[i];
+                component.transform(source, dest);
+            }
+            this.bounds = null;
+        }
+        return this;
+    },
+
+    /**
+     * APIMethod: intersects
+     * Determine if the input geometry intersects this one.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+     *
+     * Returns:
+     * {Boolean} The input geometry intersects this one.
+     */
+    intersects: function(geometry) {
+        var intersect = false;
+        for(var i=0, len=this.components.length; i<len; ++ i) {
+            intersect = geometry.intersects(this.components[i]);
+            if(intersect) {
+                break;
+            }
+        }
+        return intersect;
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.Collection"
+});
+/* ======================================================================
+    OpenLayers/Geometry/Point.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Point
+ * Point geometry class. 
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Geometry> 
+ */
+OpenLayers.Geometry.Point = OpenLayers.Class(OpenLayers.Geometry, {
+
+    /** 
+     * APIProperty: x 
+     * {float} 
+     */
+    x: null,
+
+    /** 
+     * APIProperty: y 
+     * {float} 
+     */
+    y: null,
+
+    /**
+     * Constructor: OpenLayers.Geometry.Point
+     * Construct a point geometry.
+     *
+     * Parameters:
+     * x - {float} 
+     * y - {float}
+     * 
+     */
+    initialize: function(x, y) {
+        OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+        
+        this.x = parseFloat(x);
+        this.y = parseFloat(y);
+    },
+
+    /**
+     * APIMethod: clone
+     * 
+     * Returns:
+     * {<OpenLayers.Geometry.Point>} An exact clone of this OpenLayers.Geometry.Point
+     */
+    clone: function(obj) {
+        if (obj == null) {
+            obj = new OpenLayers.Geometry.Point(this.x, this.y);
+        }
+
+        // catch any randomly tagged-on properties
+        OpenLayers.Util.applyDefaults(obj, this);
+
+        return obj;
+    },
+
+    /** 
+     * Method: calculateBounds
+     * Create a new Bounds based on the lon/lat
+     */
+    calculateBounds: function () {
+        this.bounds = new OpenLayers.Bounds(this.x, this.y,
+                                            this.x, this.y);
+    },
+
+    /**
+     * APIMethod: distanceTo
+     * 
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>} 
+     */
+    distanceTo: function(point) {
+        var distance = 0.0;
+        if ( (this.x != null) && (this.y != null) && 
+             (point != null) && (point.x != null) && (point.y != null) ) {
+             
+             var dx2 = Math.pow(this.x - point.x, 2);
+             var dy2 = Math.pow(this.y - point.y, 2);
+             distance = Math.sqrt( dx2 + dy2 );
+        }
+        return distance;
+    },
+    
+    /** 
+    * APIMethod: equals
+    * 
+    * Parameters:
+    * xy - {<OpenLayers.Geometry>} 
+    *
+    * Returns:
+    * {Boolean} Boolean value indicating whether the passed-in 
+    *          {<OpenLayers.Geometry>} object has the same  components as this
+    *          note that if ll passed in is null, returns false
+    *
+    */
+    equals:function(geom) {
+        var equals = false;
+        if (geom != null) {
+            equals = ((this.x == geom.x && this.y == geom.y) ||
+                      (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));
+        }
+        return equals;
+    },
+    
+    /**
+     * Method: toShortString
+     *
+     * Returns:
+     * {String} Shortened String representation of Point object. 
+     *         (ex. <i>"5, 42"</i>)
+     */
+    toShortString: function() {
+        return (this.x + ", " + this.y);
+    },
+    
+    /**
+     * APIMethod: move
+     * Moves a point in place
+     *
+     * Parameters:
+     * x - {Float} 
+     * y - {Float} 
+     */
+    move: function(x, y) {
+        this.x = this.x + x;
+        this.y = this.y + y;
+        this.clearBounds();
+    },
+
+    /**
+     * APIMethod: rotate
+     * Rotate a point around another.
+     *
+     * Parameters:
+     * angle - {Float} Rotation angle in degrees (measured counterclockwise
+     *                 from the positive x-axis)
+     * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+     */
+    rotate: function(angle, origin) {
+        angle *= Math.PI / 180;
+        var radius = this.distanceTo(origin);
+        var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);
+        this.x = origin.x + (radius * Math.cos(theta));
+        this.y = origin.y + (radius * Math.sin(theta));
+        this.clearBounds();
+    },
+
+    /**
+     * APIMethod: resize
+     * Resize a point relative to some origin.  For points, this has the effect
+     *     of scaling a vector (from the origin to the point).  This method is
+     *     more useful on geometry collection subclasses.
+     *
+     * Parameters:
+     * scale - {Float} Ratio of the new distance from the origin to the old
+     *                 distance from the origin.  A scale of 2 doubles the
+     *                 distance between the point and origin.
+     * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+     * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
+     */
+    resize: function(scale, origin, ratio) {
+        ratio = (ratio == undefined) ? 1 : ratio;
+        this.x = origin.x + (scale * ratio * (this.x - origin.x));
+        this.y = origin.y + (scale * (this.y - origin.y));
+        this.clearBounds();
+    },
+    
+    /**
+     * APIMethod: intersects
+     * Determine if the input geometry intersects this one.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+     *
+     * Returns:
+     * {Boolean} The input geometry intersects this one.
+     */
+    intersects: function(geometry) {
+        var intersect = false;
+        if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+            intersect = this.equals(geometry);
+        } else {
+            intersect = geometry.intersects(this);
+        }
+        return intersect;
+    },
+    
+    /**
+     * APIMethod: transform
+     * Translate the x,y properties of the point from source to dest.
+     * 
+     * Parameters:
+     * source - {<OpenLayers.Projection>} 
+     * dest - {<OpenLayers.Projection>}
+     * 
+     * Returns:
+     * {<OpenLayers.Geometry>} 
+     */
+    transform: function(source, dest) {
+        if ((source && dest)) {
+            OpenLayers.Projection.transform(
+                this, source, dest); 
+            this.bounds = null;
+        }       
+        return this;
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.Point"
+});
+/* ======================================================================
+    OpenLayers/Geometry/Rectangle.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Rectangle
+ * This class is *not supported*, and probably isn't what you're looking for.
+ *     Instead, most users probably want something like:
+ *     (code)
+ *     var poly = new OpenLayers.Bounds(0,0,10,10).toGeometry();
+ *     (end)
+ *     This will create a rectangular Polygon geometry. 
+ * 
+ * Inherits:
+ *  - <OpenLayers.Geometry>
+ */
+
+OpenLayers.Geometry.Rectangle = OpenLayers.Class(OpenLayers.Geometry, {
+
+    /** 
+     * Property: x
+     * {Float}
+     */
+    x: null,
+
+    /** 
+     * Property: y
+     * {Float}
+     */
+    y: null,
+
+    /** 
+     * Property: width
+     * {Float}
+     */
+    width: null,
+
+    /** 
+     * Property: height
+     * {Float}
+     */
+    height: null,
+
+    /**
+     * Constructor: OpenLayers.Geometry.Rectangle
+     * 
+     * Parameters:
+     * points - {Array(<OpenLayers.Geometry.Point>}
+     */
+    initialize: function(x, y, width, height) {
+        OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+        
+        this.x = x;
+        this.y = y;
+
+        this.width = width;
+        this.height = height;
+    },
+    
+    /**
+     * Method: calculateBounds
+     * Recalculate the bounds for the geometry.
+     */
+    calculateBounds: function() {
+        this.bounds = new OpenLayers.Bounds(this.x, this.y,
+                                            this.x + this.width, 
+                                            this.y + this.height);
+    },
+    
+    
+    /**
+     * APIMethod: getLength
+     * 
+     * Returns:
+     * {Float} The length of the geometry
+     */
+    getLength: function() {
+        var length = (2 * this.width) + (2 * this.height);
+        return length;
+    },
+
+    /**
+     * APIMethod: getArea
+     * 
+     * Returns:
+     * {Float} The area of the geometry
+     */
+    getArea: function() {
+        var area = this.width * this.height;
+        return area;
+    },    
+
+    CLASS_NAME: "OpenLayers.Geometry.Rectangle"
+});
+/* ======================================================================
+    OpenLayers/Geometry/Surface.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry.js
+ */
+
+OpenLayers.Geometry.Surface = OpenLayers.Class(OpenLayers.Geometry, {
+
+    initialize: function() {
+        OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.Surface"
+});
+/* ======================================================================
+    OpenLayers/Layer/MapServer/Untiled.js
+   ====================================================================== */
+
+/* Copyright 2006-2008 MetaCarta, Inc., published under the Clear BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/license.txt for the full text
+ * of the license. */
+
+ 
+/**
+ * @requires OpenLayers/Layer/MapServer.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.MapServer.Untiled
+ * *Deprecated*.  To be removed in 3.0.  Instead use OpenLayers.Layer.MapServer
+ *     and pass the option 'singleTile' as true.
+ * 
+ * Inherits from: 
+ *  - <OpenLayers.Layer.MapServer>
+ */
+OpenLayers.Layer.MapServer.Untiled = OpenLayers.Class(OpenLayers.Layer.MapServer, {
+
+    /**
+     * APIProperty: singleTile
+     * {singleTile} Always true for untiled.
+     */
+    singleTile: true,
+
+    /**
+     * Constructor: OpenLayers.Layer.MapServer.Untiled
+     *
+     * Parameters:
+     * name - {String} 
+     * url - {String} 
+     * params - {Object} 
+     * options - {Object} 
+     */
+    initialize: function(name, url, params, options) {
+        OpenLayers.Layer.MapServer.prototype.initialize.apply(this, arguments);
+        
+        var msg = "The OpenLayers.Layer.MapServer.Untiled class is deprecated and " +
+                  "will be removed in 3.0. Instead, you should use the " +
+                  "normal OpenLayers.Layer.MapServer class, passing it the option " +
+                  "'singleTile' as true.";
+        OpenLayers.Console.warn(msg);
+    },    
+
+    /**
+     * Method: clone
+     * Create a clone of this layer
+     *
+     * Returns:
+     * {<OpenLayers.Layer.MapServer.Untiled>} An exact clone of this layer
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.MapServer.Untiled(this.name,
+                                                         this.url,
+                                                         this.params,
+                                                         this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.MapServer.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    }, 
+
+    CLASS_NAME: "OpenLayers.Layer.MapServer.Untiled"
+});
+/* ======================================================================
+    OpenLayers/Layer/Vector.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer.js
+ * @requires OpenLayers/Renderer.js
+ * @requires OpenLayers/StyleMap.js
+ * @requires OpenLayers/Feature/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.Vector
+ * Instances of OpenLayers.Layer.Vector are used to render vector data from
+ *     a variety of sources. Create a new vector layer with the
+ *     <OpenLayers.Layer.Vector> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer>
+ */
+OpenLayers.Layer.Vector = OpenLayers.Class(OpenLayers.Layer, {
+
+    /**
+     * Constant: EVENT_TYPES
+     * {Array(String)} Supported application event types.  Register a listener
+     *     for a particular event with the following syntax:
+     * (code)
+     * layer.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 layer.events.object.
+     *  - *element* {DOMElement} A reference to layer.events.element.
+     *
+     * Supported map event types (in addition to those from <OpenLayers.Layer>):
+     *  - *beforefeatureadded* Triggered before a feature is added.  Listeners
+     *      will receive an object with a *feature* property referencing the
+     *      feature to be added.
+     *  - *featureadded* Triggered after a feature is added.  The event
+     *      object passed to listeners will have a *feature* property with a
+     *      reference to the added feature.
+     *  - *featuresadded* Triggered after features are added.  The event
+     *      object passed to listeners will have a *features* property with a
+     *      reference to an array of added features.
+     *  - *beforefeatureremoved* Triggered before a feature is removed. Listeners
+     *      will receive an object with a *feature* property referencing the
+     *      feature to be removed.
+     *  - *featureremoved* Triggerd after a feature is removed. The event
+     *      object passed to listeners will have a *feature* property with a
+     *      reference to the removed feature.
+     *  - *featuresremoved* Triggered after features are removed. The event
+     *      object passed to listeners will have a *features* property with a
+     *      reference to an array of removed features.
+     *  - *featureselected* Triggered after a feature is selected.  Listeners
+     *      will receive an object with a *feature* property referencing the
+     *      selected feature.
+     *  - *featureunselected* Triggered after a feature is unselected.
+     *      Listeners will receive an object with a *feature* property
+     *      referencing the unselected feature.
+     *  - *beforefeaturemodified* Triggered when a feature is selected to 
+     *      be modified.  Listeners will receive an object with a *feature* 
+     *      property referencing the selected feature.
+     *  - *featuremodified* Triggered when a feature has been modified.
+     *      Listeners will receive an object with a *feature* property referencing 
+     *      the modified feature.
+     *  - *afterfeaturemodified* Triggered when a feature is finished being modified.
+     *      Listeners will receive an object with a *feature* property referencing 
+     *      the modified feature.
+     */
+    EVENT_TYPES: ["beforefeatureadded", "featureadded", "featuresadded",
+                  "beforefeatureremoved", "featureremoved", "featuresremoved",
+                  "beforefeatureselected", "featureselected", "featureunselected", 
+                  "beforefeaturemodified", "featuremodified", "afterfeaturemodified"],
+
+    /**
+     * APIProperty: isBaseLayer
+     * {Boolean} The layer is a base layer.  Default is true.  Set this property
+     * in the layer options
+     */
+    isBaseLayer: false,
+
+    /** 
+     * APIProperty: isFixed
+     * {Boolean} Whether the layer remains in one place while dragging the
+     * map.
+     */
+    isFixed: false,
+
+    /** 
+     * APIProperty: isVector
+     * {Boolean} Whether the layer is a vector layer.
+     */
+    isVector: true,
+
+    /** 
+     * APIProperty: features
+     * {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    features: null,
+    
+    /** 
+     * Property: selectedFeatures
+     * {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    selectedFeatures: null,
+    
+    /**
+     * Property: unrenderedFeatures
+     * {Object} hash of features, keyed by feature.id, that the renderer
+     *     failed to draw
+     */
+    unrenderedFeatures: null,
+
+    /**
+     * APIProperty: reportError
+     * {Boolean} report friendly error message when loading of renderer
+     * fails.
+     */
+    reportError: true, 
+
+    /** 
+     * APIProperty: style
+     * {Object} Default style for the layer
+     */
+    style: null,
+    
+    /**
+     * Property: styleMap
+     * {<OpenLayers.StyleMap>}
+     */
+    styleMap: null,
+    
+    /**
+     * Property: strategies
+     * {Array(<OpenLayers.Strategy>})} Optional list of strategies for the layer.
+     */
+    strategies: null,
+    
+    /**
+     * Property: protocol
+     * {<OpenLayers.Protocol>} Optional protocol for the layer.
+     */
+    protocol: null,
+    
+    /**
+     * Property: renderers
+     * {Array(String)} List of supported Renderer classes. Add to this list to
+     * add support for additional renderers. This list is ordered:
+     * the first renderer which returns true for the  'supported()'
+     * method will be used, if not defined in the 'renderer' option.
+     */
+    renderers: ['SVG', 'VML', 'Canvas'],
+    
+    /** 
+     * Property: renderer
+     * {<OpenLayers.Renderer>}
+     */
+    renderer: null,
+    
+    /**
+     * APIProperty: rendererOptions
+     * {Object} Options for the renderer. See {<OpenLayers.Renderer>} for
+     *     supported options.
+     */
+    rendererOptions: null,
+    
+    /** 
+     * APIProperty: geometryType
+     * {String} geometryType allows you to limit the types of geometries this
+     * layer supports. This should be set to something like
+     * "OpenLayers.Geometry.Point" to limit types.
+     */
+    geometryType: null,
+
+    /** 
+     * Property: drawn
+     * {Boolean} Whether the Vector Layer features have been drawn yet.
+     */
+    drawn: false,
+
+    /**
+     * Constructor: OpenLayers.Layer.Vector
+     * Create a new vector layer
+     *
+     * Parameters:
+     * name - {String} A name for the layer
+     * options - {Object} Optional object with non-default properties to set on
+     *           the layer.
+     *
+     * Returns:
+     * {<OpenLayers.Layer.Vector>} A new vector layer
+     */
+    initialize: function(name, options) {
+        
+        // concatenate events specific to vector with those from the base
+        this.EVENT_TYPES =
+            OpenLayers.Layer.Vector.prototype.EVENT_TYPES.concat(
+            OpenLayers.Layer.prototype.EVENT_TYPES
+        );
+
+        OpenLayers.Layer.prototype.initialize.apply(this, arguments);
+
+        // allow user-set renderer, otherwise assign one
+        if (!this.renderer || !this.renderer.supported()) {  
+            this.assignRenderer();
+        }
+
+        // if no valid renderer found, display error
+        if (!this.renderer || !this.renderer.supported()) {
+            this.renderer = null;
+            this.displayError();
+        } 
+
+        if (!this.styleMap) {
+            this.styleMap = new OpenLayers.StyleMap();
+        }
+
+        this.features = [];
+        this.selectedFeatures = [];
+        this.unrenderedFeatures = {};
+        
+        // Allow for custom layer behavior
+        if(this.strategies){
+            for(var i=0, len=this.strategies.length; i<len; i++) {
+                this.strategies[i].setLayer(this);
+            }
+        }
+
+    },
+
+    /**
+     * APIMethod: destroy
+     * Destroy this layer
+     */
+    destroy: function() {
+        if (this.strategies) {
+            var strategy, i, len;
+            for(i=0, len=this.strategies.length; i<len; i++) {
+                strategy = this.strategies[i];
+                if(strategy.autoDestroy) {
+                    strategy.destroy();
+                }
+            }
+            this.strategies = null;
+        }
+        if (this.protocol) {
+            if(this.protocol.autoDestroy) {
+                this.protocol.destroy();
+            }
+            this.protocol = null;
+        }
+        this.destroyFeatures();
+        this.features = null;
+        this.selectedFeatures = null;
+        this.unrenderedFeatures = null;
+        if (this.renderer) {
+            this.renderer.destroy();
+        }
+        this.renderer = null;
+        this.geometryType = null;
+        this.drawn = null;
+        OpenLayers.Layer.prototype.destroy.apply(this, arguments);  
+    },
+
+    /** 
+     * Method: assignRenderer
+     * Iterates through the available renderer implementations and selects 
+     * and assigns the first one whose "supported()" function returns true.
+     */    
+    assignRenderer: function()  {
+        for (var i=0, len=this.renderers.length; i<this.renderers.length; i++) {
+            var rendererClass = OpenLayers.Renderer[this.renderers[i]];
+            if (rendererClass && rendererClass.prototype.supported()) {
+                this.renderer = new rendererClass(this.div,
+                    this.rendererOptions);
+                break;
+            }  
+        }  
+    },
+
+    /** 
+     * Method: displayError 
+     * Let the user know their browser isn't supported.
+     */
+    displayError: function() {
+        if (this.reportError) {
+            OpenLayers.Console.userError(OpenLayers.i18n("browserNotSupported", 
+                                     {'renderers':this.renderers.join("\n")}));
+        }    
+    },
+
+    /** 
+     * Method: setMap
+     * The layer has been added to the map. 
+     * 
+     * If there is no renderer set, the layer can't be used. Remove it.
+     * Otherwise, give the renderer a reference to the map and set its size.
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {        
+        OpenLayers.Layer.prototype.setMap.apply(this, arguments);
+
+        if (!this.renderer) {
+            this.map.removeLayer(this);
+        } else {
+            this.renderer.map = this.map;
+            this.renderer.setSize(this.map.getSize());
+        }
+        if(this.strategies) {
+            var strategy, i, len;
+            for(i=0, len=this.strategies.length; i<len; i++) {
+                strategy = this.strategies[i];
+                if(strategy.autoActivate) {
+                    strategy.activate();
+                }
+            }
+        }
+    },
+
+    /**
+     * Method: removeMap
+     * The layer has been removed from the map.
+     *
+     * Parameters:
+     * map - {<OpenLayers.Map>}
+     */
+    removeMap: function(map) {
+        if(this.strategies) {
+            var strategy, i, len;
+            for(i=0, len=this.strategies.length; i<len; i++) {
+                strategy = this.strategies[i];
+                if(strategy.autoActivate) {
+                    strategy.deactivate();
+                }
+            }
+        }
+    },
+    
+    /**
+     * Method: onMapResize
+     * Notify the renderer of the change in size. 
+     * 
+     */
+    onMapResize: function() {
+        OpenLayers.Layer.prototype.onMapResize.apply(this, arguments);
+        this.renderer.setSize(this.map.getSize());
+    },
+
+    /**
+     * Method: moveTo
+     *  Reset the vector layer's div so that it once again is lined up with 
+     *   the map. Notify the renderer of the change of extent, and in the
+     *   case of a change of zoom level (resolution), have the 
+     *   renderer redraw features.
+     * 
+     *  If the layer has not yet been drawn, cycle through the layer's 
+     *   features and draw each one.
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     * zoomChanged - {Boolean} 
+     * dragging - {Boolean} 
+     */
+    moveTo: function(bounds, zoomChanged, dragging) {
+        OpenLayers.Layer.prototype.moveTo.apply(this, arguments);
+        
+        var coordSysUnchanged = true;
+
+        if (!dragging) {
+            this.renderer.root.style.visibility = "hidden";
+            
+            this.div.style.left = -parseInt(this.map.layerContainerDiv.style.left) + "px";
+            this.div.style.top = -parseInt(this.map.layerContainerDiv.style.top) + "px";
+            var extent = this.map.getExtent();
+            coordSysUnchanged = this.renderer.setExtent(extent, zoomChanged);
+            
+            this.renderer.root.style.visibility = "visible";
+
+            // Force a reflow on gecko based browsers to prevent jump/flicker.
+            // This seems to happen on only certain configurations; it was originally
+            // noticed in FF 2.0 and Linux.
+            if (navigator.userAgent.toLowerCase().indexOf("gecko") != -1) {
+                this.div.scrollLeft = this.div.scrollLeft;
+            }
+            
+            if(!zoomChanged && coordSysUnchanged) {
+                var unrenderedFeatures = {};
+                for(var i in this.unrenderedFeatures) {
+                    var feature = this.unrenderedFeatures[i];
+                    if(!this.drawFeature(feature)) {
+                        unrenderedFeatures[i] = feature;
+                    }
+                }
+                this.unrenderedFeatures = unrenderedFeatures;
+            }
+        }
+        
+        if (!this.drawn || zoomChanged || !coordSysUnchanged) {
+            this.unrenderedFeatures = {};
+            this.drawn = true;
+            var feature;
+            for(var i=0, len=this.features.length; i<len; i++) {
+                if (i != (this.features.length - 1)) {
+                    this.renderer.locked = true;
+                } else {
+                    this.renderer.locked = false;
+                }    
+                feature = this.features[i];
+                if (!this.drawFeature(feature)) {
+                    this.unrenderedFeatures[feature.id] = feature;
+                };
+            }
+        }    
+    },
+
+    /**
+     * APIMethod: addFeatures
+     * Add Features to the layer.
+     *
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     * options - {Object}
+     */
+    addFeatures: function(features, options) {
+        if (!(features instanceof Array)) {
+            features = [features];
+        }
+        
+        var notify = !options || !options.silent;
+
+        for (var i=0, len=features.length; i<len; i++) {
+            if (i != (features.length - 1)) {
+                this.renderer.locked = true;
+            } else {
+                this.renderer.locked = false;
+            }    
+            var feature = features[i];
+            
+            if (this.geometryType &&
+              !(feature.geometry instanceof this.geometryType)) {
+                var throwStr = OpenLayers.i18n('componentShouldBe',
+                          {'geomType':this.geometryType.prototype.CLASS_NAME});
+                throw throwStr;
+              }
+
+            this.features.push(feature);
+            
+            //give feature reference to its layer
+            feature.layer = this;
+
+            if (!feature.style && this.style) {
+                feature.style = OpenLayers.Util.extend({}, this.style);
+            }
+
+            if (notify) {
+                this.events.triggerEvent("beforefeatureadded", {
+                    feature: feature
+                });
+                this.preFeatureInsert(feature);
+            }
+
+            if (this.drawn) {
+                if(!this.drawFeature(feature)) {
+                    this.unrenderedFeatures[feature.id] = feature;
+                }
+            }
+            
+            if (notify) {
+                this.events.triggerEvent("featureadded", {
+                    feature: feature
+                });
+                this.onFeatureInsert(feature);
+            }
+        }
+        
+        if(notify) {
+            this.events.triggerEvent("featuresadded", {features: features});
+        }
+    },
+
+
+    /**
+     * APIMethod: removeFeatures
+     * 
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     * options - {Object}
+     */
+    removeFeatures: function(features, options) {
+        if (!(features instanceof Array)) {
+            features = [features];
+        }
+        if (features.length <= 0) {
+            return;
+        }
+
+        var notify = !options || !options.silent;
+
+        for (var i = features.length - 1; i >= 0; i--) {
+            // We remain locked so long as we're not at 0
+            // and the 'next' feature has a geometry. We do the geometry check
+            // because if all the features after the current one are 'null', we
+            // won't call eraseGeometry, so we break the 'renderer functions
+            // will always be called with locked=false *last*' rule. The end result
+            // is a possible gratiutious unlocking to save a loop through the rest 
+            // of the list checking the remaining features every time. So long as
+            // null geoms are rare, this is probably okay.    
+            if (i != 0 && features[i-1].geometry) {
+                this.renderer.locked = true;
+            } else {
+                this.renderer.locked = false;
+            }
+    
+            var feature = features[i];
+            delete this.unrenderedFeatures[feature.id];
+
+            if (notify) {
+                this.events.triggerEvent("beforefeatureremoved", {
+                    feature: feature
+                });
+            }
+
+            this.features = OpenLayers.Util.removeItem(this.features, feature);
+            // feature has no layer at this point
+            feature.layer = null;
+
+            if (feature.geometry) {
+                this.renderer.eraseGeometry(feature.geometry);
+            }
+                    
+            //in the case that this feature is one of the selected features, 
+            // remove it from that array as well.
+            if (OpenLayers.Util.indexOf(this.selectedFeatures, feature) != -1){
+                OpenLayers.Util.removeItem(this.selectedFeatures, feature);
+            }
+
+            if (notify) {
+                this.events.triggerEvent("featureremoved", {
+                    feature: feature
+                });
+            }
+        }
+
+        if (notify) {
+            this.events.triggerEvent("featuresremoved", {features: features});
+        }
+    },
+
+    /**
+     * APIMethod: destroyFeatures
+     * Erase and destroy features on the layer.
+     *
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} An optional array of
+     *     features to destroy.  If not supplied, all features on the layer
+     *     will be destroyed.
+     * options - {Object}
+     */
+    destroyFeatures: function(features, options) {
+        var all = (features == undefined); // evaluates to true if
+                                           // features is null
+        if(all) {
+            features = this.features;
+        }
+        this.removeFeatures(features, options);
+        for (var i = 0; i < features.length; i++) {
+            features[i].destroy();
+        }
+    },
+
+    /**
+     * APIMethod: drawFeature
+     * Draw (or redraw) a feature on the layer.  If the optional style argument
+     * is included, this style will be used.  If no style is included, the
+     * feature's style will be used.  If the feature doesn't have a style,
+     * the layer's style will be used.
+     * 
+     * Parameters: 
+     * feature - {<OpenLayers.Feature.Vector>} 
+     * style - {Object} Symbolizer hash or {String} renderIntent
+     * 
+     * Returns:
+     * {Boolean} true if the renderer was able to draw the feature, false
+     *     otherwise
+     */
+    drawFeature: function(feature, style) {
+        if (typeof style != "object") {
+            var renderIntent = typeof style == "string" ?
+                style : feature.renderIntent;
+            style = feature.style || this.style;
+            if (!style) {
+                style = this.styleMap.createSymbolizer(feature, renderIntent);
+            }
+        }
+        
+        return this.renderer.drawFeature(feature, style);
+    },
+    
+    /**
+     * Method: eraseFeatures
+     * Erase features from the layer.
+     *
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    eraseFeatures: function(features) {
+        this.renderer.eraseFeatures(features);
+    },
+
+    /**
+     * Method: getFeatureFromEvent
+     * Given an event, return a feature if the event occurred over one.
+     * Otherwise, return null.
+     *
+     * Parameters:
+     * evt - {Event} 
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A feature if one was under the event.
+     */
+    getFeatureFromEvent: function(evt) {
+        if (!this.renderer) {
+            OpenLayers.Console.error(OpenLayers.i18n("getFeatureError")); 
+            return null;
+        }    
+        var featureId = this.renderer.getFeatureIdFromEvent(evt);
+        return this.getFeatureById(featureId);
+    },
+    
+    /**
+     * APIMethod: getFeatureById
+     * Given a feature id, return the feature if it exists in the features array
+     *
+     * Parameters:
+     * featureId - {String} 
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A feature corresponding to the given
+     * featureId
+     */
+    getFeatureById: function(featureId) {
+        //TBD - would it be more efficient to use a hash for this.features?
+        var feature = null;
+        for(var i=0, len=this.features.length; i<len; ++i) {
+            if(this.features[i].id == featureId) {
+                feature = this.features[i];
+                break;
+            }
+        }
+        return feature;
+    },
+    
+    /**
+     * Unselect the selected features
+     * i.e. clears the featureSelection array
+     * change the style back
+    clearSelection: function() {
+
+       var vectorLayer = this.map.vectorLayer;
+        for (var i = 0; i < this.map.featureSelection.length; i++) {
+            var featureSelection = this.map.featureSelection[i];
+            vectorLayer.drawFeature(featureSelection, vectorLayer.style);
+        }
+        this.map.featureSelection = [];
+    },
+     */
+
+
+    /**
+     * APIMethod: onFeatureInsert
+     * method called after a feature is inserted.
+     * Does nothing by default. Override this if you
+     * need to do something on feature updates.
+     *
+     * Paarameters: 
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    onFeatureInsert: function(feature) {
+    },
+    
+    /**
+     * APIMethod: preFeatureInsert
+     * method called before a feature is inserted.
+     * Does nothing by default. Override this if you
+     * need to do something when features are first added to the
+     * layer, but before they are drawn, such as adjust the style.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    preFeatureInsert: function(feature) {
+    },
+
+    /** 
+     * APIMethod: getDataExtent
+     * Calculates the max extent which includes all of the features.
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>}
+     */
+    getDataExtent: function () {
+        var maxExtent = null;
+        if( this.features && (this.features.length > 0)){
+            var maxExtent = this.features[0].geometry.getBounds();
+            for(var i=0, len=this.features.length; i<len; i++){
+                maxExtent.extend(this.features[i].geometry.getBounds());
+            }
+        }
+
+        return maxExtent;
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.Vector"
+});
+/* ======================================================================
+    OpenLayers/Layer/WMS/Untiled.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+ 
+/**
+ * @requires OpenLayers/Layer/WMS.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WMS.Untiled
+ * *Deprecated*.  To be removed in 3.0.  Instead use OpenLayers.Layer.WMS and 
+ *     pass the option 'singleTile' as true.
+ * 
+ * Inherits from: 
+ *  - <OpenLayers.Layer.WMS>
+ */
+OpenLayers.Layer.WMS.Untiled = OpenLayers.Class(OpenLayers.Layer.WMS, {
+
+    /**
+     * APIProperty: singleTile
+     * {singleTile} Always true for untiled.
+     */
+    singleTile: true,
+
+    /**
+     * Constructor: OpenLayers.Layer.WMS.Untiled
+     *
+     * Parameters:
+     * name - {String} 
+     * url - {String} 
+     * params - {Object} 
+     * options - {Object} 
+     */
+    initialize: function(name, url, params, options) {
+        OpenLayers.Layer.WMS.prototype.initialize.apply(this, arguments);
+        
+        var msg = "The OpenLayers.Layer.WMS.Untiled class is deprecated and " +
+                  "will be removed in 3.0. Instead, you should use the " +
+                  "normal OpenLayers.Layer.WMS class, passing it the option " +
+                  "'singleTile' as true.";
+        OpenLayers.Console.warn(msg);
+    },    
+
+    /**
+     * Method: clone
+     * Create a clone of this layer
+     *
+     * Returns:
+     * {<OpenLayers.Layer.WMS.Untiled>} An exact clone of this layer
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.WMS.Untiled(this.name,
+                                                   this.url,
+                                                   this.params,
+                                                   this.options);
+        }
+
+        //get all additions from superclasses
+        obj = OpenLayers.Layer.WMS.prototype.clone.apply(this, [obj]);
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    }, 
+
+    CLASS_NAME: "OpenLayers.Layer.WMS.Untiled"
+});
+/* ======================================================================
+    OpenLayers/Format/Filter.js
+   ====================================================================== */
+
+/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter
+ * Read/Wite ogc:Filter. Create a new instance with the <OpenLayers.Format.Filter>
+ *     constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.Filter = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * APIProperty: defaultVersion
+     * {String} Version number to assume if none found.  Default is "1.0.0".
+     */
+    defaultVersion: "1.0.0",
+    
+    /**
+     * APIProperty: version
+     * {String} Specify a version string if one is known.
+     */
+    version: null,
+    
+    /**
+     * Property: parser
+     * {Object} Instance of the versioned parser.  Cached for multiple read and
+     *     write calls of the same version.
+     */
+    parser: null,
+
+    /**
+     * Constructor: OpenLayers.Format.Filter
+     * Create a new parser for Filter.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: write
+     * Write an ogc:Filter given a filter object.
+     *
+     * Parameters:
+     * filter - {<OpenLayers.Filter>} An filter.
+     * options - {Object} Optional configuration object.
+     *
+     * Returns:
+     * {Elment} An ogc:Filter element node.
+     */
+    write: function(filter, options) {
+        var version = (options && options.version) ||
+                      this.version || this.defaultVersion;
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.Filter[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a Filter parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        return this.parser.write(filter);
+        //return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+    },
+    
+    /**
+     * APIMethod: read
+     * Read and Filter doc and return an object representing the Filter.
+     *
+     * Parameters:
+     * data - {String | DOMElement} Data to read.
+     *
+     * Returns:
+     * {<OpenLayers.Filter>} A filter object.
+     */
+    read: function(data) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var root = data.nodeType == 9 ? data.documentElement : data;
+        var version = this.version;
+        if(!version) {
+            version = this.defaultVersion;
+        }
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.Filter[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a Filter parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        var filter = this.parser.read(data);
+        return filter;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.Filter" 
+});
+/* ======================================================================
+    OpenLayers/Format/SLD.js
+   ====================================================================== */
+
+/* Copyright (c) 2006 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Style.js
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD
+ * Read/Wite SLD. Create a new instance with the <OpenLayers.Format.SLD>
+ *     constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * APIProperty: defaultVersion
+     * {String} Version number to assume if none found.  Default is "1.0.0".
+     */
+    defaultVersion: "1.0.0",
+    
+    /**
+     * APIProperty: version
+     * {String} Specify a version string if one is known.
+     */
+    version: null,
+    
+    /**
+     * Property: parser
+     * {Object} Instance of the versioned parser.  Cached for multiple read and
+     *     write calls of the same version.
+     */
+    parser: null,
+
+    /**
+     * Constructor: OpenLayers.Format.SLD
+     * Create a new parser for SLD.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: write
+     * Write a SLD document given a list of styles.
+     *
+     * Parameters:
+     * sld - {Object} An object representing the SLD.
+     * options - {Object} Optional configuration object.
+     *
+     * Returns:
+     * {String} An SLD document string.
+     */
+    write: function(sld, options) {
+        var version = (options && options.version) ||
+                      this.version || this.defaultVersion;
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.SLD[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a SLD parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        var root = this.parser.write(sld);
+        return OpenLayers.Format.XML.prototype.write.apply(this, [root]);
+    },
+    
+    /**
+     * APIMethod: read
+     * Read and SLD doc and return an object representing the SLD.
+     *
+     * Parameters:
+     * data - {String | DOMElement} Data to read.
+     *
+     * Returns:
+     * {Object} An object representing the SLD.
+     */
+    read: function(data) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var root = data.documentElement;
+        var version = this.version;
+        if(!version) {
+            version = root.getAttribute("version");
+            if(!version) {
+                version = this.defaultVersion;
+            }
+        }
+        if(!this.parser || this.parser.VERSION != version) {
+            var format = OpenLayers.Format.SLD[
+                "v" + version.replace(/\./g, "_")
+            ];
+            if(!format) {
+                throw "Can't find a SLD parser for version " +
+                      version;
+            }
+            this.parser = new format(this.options);
+        }
+        var sld = this.parser.read(data);
+        return sld;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.SLD" 
+});
+/* ======================================================================
+    OpenLayers/Format/Text.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Text
+ * Read Text format. Create a new instance with the <OpenLayers.Format.Text>
+ *     constructor. This reads text which is formatted like CSV text, using
+ *     tabs as the seperator by default. It provides parsing of data originally
+ *     used in the MapViewerService, described on the wiki. This Format is used
+ *     by the <OpenLayers.Layer.Text> class.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.Text = OpenLayers.Class(OpenLayers.Format, {
+    
+    /**
+     * Constructor: OpenLayers.Format.Text
+     * Create a new parser for TSV Text.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.prototype.initialize.apply(this, [options]);
+    }, 
+
+    /**
+     * APIMethod: read
+     * Return a list of features from a Tab Seperated Values text string.
+     * 
+     * Parameters:
+     * data - {String} 
+     *
+     * Returns:
+     * An Array of <OpenLayers.Feature.Vector>s
+     */
+    read: function(text) {
+        var lines = text.split('\n');
+        var columns;
+        var features = [];
+        // length - 1 to allow for trailing new line
+        for (var lcv = 0; lcv < (lines.length - 1); lcv++) {
+            var currLine = lines[lcv].replace(/^\s*/,'').replace(/\s*$/,'');
+        
+            if (currLine.charAt(0) != '#') { /* not a comment */
+            
+                if (!columns) {
+                    //First line is columns
+                    columns = currLine.split('\t');
+                } else {
+                    var vals = currLine.split('\t');
+                    var geometry = new OpenLayers.Geometry.Point(0,0);
+                    var attributes = {};
+                    var style = {};
+                    var icon, iconSize, iconOffset, overflow;
+                    var set = false;
+                    for (var valIndex = 0; valIndex < vals.length; valIndex++) {
+                        if (vals[valIndex]) {
+                            if (columns[valIndex] == 'point') {
+                                var coords = vals[valIndex].split(',');
+                                geometry.y = parseFloat(coords[0]);
+                                geometry.x = parseFloat(coords[1]);
+                                set = true;
+                            } else if (columns[valIndex] == 'lat') {
+                                geometry.y = parseFloat(vals[valIndex]);
+                                set = true;
+                            } else if (columns[valIndex] == 'lon') {
+                                geometry.x = parseFloat(vals[valIndex]);
+                                set = true;
+                            } else if (columns[valIndex] == 'title')
+                                attributes['title'] = vals[valIndex];
+                            else if (columns[valIndex] == 'image' ||
+                                     columns[valIndex] == 'icon')
+                                style['externalGraphic'] = vals[valIndex];
+                            else if (columns[valIndex] == 'iconSize') {
+                                var size = vals[valIndex].split(',');
+                                style['graphicWidth'] = parseFloat(size[0]);
+                                style['graphicHeight'] = parseFloat(size[1]);
+                            } else if (columns[valIndex] == 'iconOffset') {
+                                var offset = vals[valIndex].split(',');
+                                style['graphicXOffset'] = parseFloat(offset[0]);
+                                style['graphicYOffset'] = parseFloat(offset[1]);
+                            } else if (columns[valIndex] == 'description') {
+                                attributes['description'] = vals[valIndex];
+                            } else if (columns[valIndex] == 'overflow') {
+                                attributes['overflow'] = vals[valIndex];
+                            }    
+                        }
+                    }
+                    if (set) {
+                      if (this.internalProjection && this.externalProjection) {
+                          geometry.transform(this.externalProjection, 
+                                             this.internalProjection); 
+                      }                       
+                      var feature = new OpenLayers.Feature.Vector(geometry, attributes, style);
+                      features.push(feature);
+                    }
+                }
+            }
+        }
+        return features;
+    },   
+
+    CLASS_NAME: "OpenLayers.Format.Text" 
+});    
+/* ======================================================================
+    OpenLayers/Geometry/MultiLineString.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiLineString
+ * A MultiLineString is a geometry with multiple <OpenLayers.Geometry.LineString>
+ * components.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Geometry.Collection>
+ *  - <OpenLayers.Geometry> 
+ */
+OpenLayers.Geometry.MultiLineString = OpenLayers.Class(
+  OpenLayers.Geometry.Collection, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of
+     * components that the collection can include.  A null value means the
+     * component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.LineString"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.MultiLineString
+     * Constructor for a MultiLineString Geometry.
+     *
+     * Parameters: 
+     * components - {Array(<OpenLayers.Geometry.LineString>)} 
+     *
+     */
+    initialize: function(components) {
+        OpenLayers.Geometry.Collection.prototype.initialize.apply(this, 
+                                                                  arguments);        
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.MultiLineString"
+});
+/* ======================================================================
+    OpenLayers/Geometry/MultiPoint.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPoint
+ * MultiPoint is a collection of Points.  Create a new instance with the
+ * <OpenLayers.Geometry.MultiPoint> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Geometry.Collection>
+ *  - <OpenLayers.Geometry>
+ */
+OpenLayers.Geometry.MultiPoint = OpenLayers.Class(
+  OpenLayers.Geometry.Collection, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of
+     * components that the collection can include.  A null value means the
+     * component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.Point"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.MultiPoint
+     * Create a new MultiPoint Geometry
+     *
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry.Point>)} 
+     *
+     * Returns:
+     * {<OpenLayers.Geometry.MultiPoint>}
+     */
+    initialize: function(components) {
+        OpenLayers.Geometry.Collection.prototype.initialize.apply(this, 
+                                                                  arguments);
+    },
+
+    /**
+     * APIMethod: addPoint
+     * Wrapper for <OpenLayers.Geometry.Collection.addComponent>
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>} Point to be added
+     * index - {Integer} Optional index
+     */
+    addPoint: function(point, index) {
+        this.addComponent(point, index);
+    },
+    
+    /**
+     * APIMethod: removePoint
+     * Wrapper for <OpenLayers.Geometry.Collection.removeComponent>
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>} Point to be removed
+     */
+    removePoint: function(point){
+        this.removeComponent(point);
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.MultiPoint"
+});
+/* ======================================================================
+    OpenLayers/Geometry/MultiPolygon.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.MultiPolygon
+ * MultiPolygon is a geometry with multiple <OpenLayers.Geometry.Polygon>
+ * components.  Create a new instance with the <OpenLayers.Geometry.MultiPolygon>
+ * constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Geometry.Collection>
+ */
+OpenLayers.Geometry.MultiPolygon = OpenLayers.Class(
+  OpenLayers.Geometry.Collection, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of
+     * components that the collection can include.  A null value means the
+     * component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.Polygon"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.MultiPolygon
+     * Create a new MultiPolygon geometry
+     *
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry.Polygon>)} An array of polygons
+     *              used to generate the MultiPolygon
+     *
+     */
+    initialize: function(components) {
+        OpenLayers.Geometry.Collection.prototype.initialize.apply(this, 
+                                                                  arguments);
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.MultiPolygon"
+});
+/* ======================================================================
+    OpenLayers/Geometry/Polygon.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Collection.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Polygon 
+ * Polygon is a collection of Geometry.LinearRings. 
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Geometry.Collection> 
+ *  - <OpenLayers.Geometry> 
+ */
+OpenLayers.Geometry.Polygon = OpenLayers.Class(
+  OpenLayers.Geometry.Collection, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of
+     * components that the collection can include.  A null value means the
+     * component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.LinearRing"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.Polygon
+     * Constructor for a Polygon geometry. 
+     * The first ring (this.component[0])is the outer bounds of the polygon and 
+     * all subsequent rings (this.component[1-n]) are internal holes.
+     *
+     *
+     * Parameters:
+     * components - {Array(<OpenLayers.Geometry.LinearRing>)} 
+     */
+    initialize: function(components) {
+        OpenLayers.Geometry.Collection.prototype.initialize.apply(this, 
+                                                                  arguments);
+    },
+    
+    /** 
+     * APIMethod: getArea
+     * Calculated by subtracting the areas of the internal holes from the 
+     *   area of the outer hole.
+     * 
+     * Returns:
+     * {float} The area of the geometry
+     */
+    getArea: function() {
+        var area = 0.0;
+        if ( this.components && (this.components.length > 0)) {
+            area += Math.abs(this.components[0].getArea());
+            for (var i=1, len=this.components.length; i<len; i++) {
+                area -= Math.abs(this.components[i].getArea());
+            }
+        }
+        return area;
+    },
+
+    /**
+     * Method: containsPoint
+     * Test if a point is inside a polygon.  Points on a polygon edge are
+     *     considered inside.
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>}
+     *
+     * Returns:
+     * {Boolean | Number} The point is inside the polygon.  Returns 1 if the
+     *     point is on an edge.  Returns boolean otherwise.
+     */
+    containsPoint: function(point) {
+        var numRings = this.components.length;
+        var contained = false;
+        if(numRings > 0) {
+            // check exterior ring - 1 means on edge, boolean otherwise
+            contained = this.components[0].containsPoint(point);
+            if(contained !== 1) {
+                if(contained && numRings > 1) {
+                    // check interior rings
+                    var hole;
+                    for(var i=1; i<numRings; ++i) {
+                        hole = this.components[i].containsPoint(point);
+                        if(hole) {
+                            if(hole === 1) {
+                                // on edge
+                                contained = 1;
+                            } else {
+                                // in hole
+                                contained = false;
+                            }                            
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+        return contained;
+    },
+
+    /**
+     * APIMethod: intersects
+     * Determine if the input geometry intersects this one.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+     *
+     * Returns:
+     * {Boolean} The input geometry intersects this one.
+     */
+    intersects: function(geometry) {
+        var intersect = false;
+        var i, len;
+        if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+            intersect = this.containsPoint(geometry);
+        } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString" ||
+                  geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+            // check if rings/linestrings intersect
+            for(i=0, len=this.components.length; i<len; ++i) {
+                intersect = geometry.intersects(this.components[i]);
+                if(intersect) {
+                    break;
+                }
+            }
+            if(!intersect) {
+                // check if this poly contains points of the ring/linestring
+                for(i=0, len=geometry.components.length; i<len; ++i) {
+                    intersect = this.containsPoint(geometry.components[i]);
+                    if(intersect) {
+                        break;
+                    }
+                }
+            }
+        } else {
+            for(i=0, len=geometry.components.length; i<len; ++ i) {
+                intersect = this.intersects(geometry.components[i]);
+                if(intersect) {
+                    break;
+                }
+            }
+        }
+        // check case where this poly is wholly contained by another
+        if(!intersect && geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+            // exterior ring points will be contained in the other geometry
+            var ring = this.components[0];
+            for(i=0, len=ring.components.length; i<len; ++i) {
+                intersect = geometry.containsPoint(ring.components[i]);
+                if(intersect) {
+                    break;
+                }
+            }
+        }
+        return intersect;
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.Polygon"
+});
+
+/**
+ * APIMethod: createRegularPolygon
+ * Create a regular polygon around a radius. Useful for creating circles 
+ * and the like.
+ *
+ * Parameters:
+ * origin - {<OpenLayers.Geometry.Point>} center of polygon.
+ * radius - {Float} distance to vertex, in map units.
+ * sides - {Integer} Number of sides. 20 approximates a circle.
+ * rotation - {Float} original angle of rotation, in degrees.
+ */
+OpenLayers.Geometry.Polygon.createRegularPolygon = function(origin, radius, sides, rotation) {  
+    var angle = Math.PI * ((1/sides) - (1/2));
+    if(rotation) {
+        angle += (rotation / 180) * Math.PI;
+    }
+    var rotatedAngle, x, y;
+    var points = [];
+    for(var i=0; i<sides; ++i) {
+        rotatedAngle = angle + (i * 2 * Math.PI / sides);
+        x = origin.x + (radius * Math.cos(rotatedAngle));
+        y = origin.y + (radius * Math.sin(rotatedAngle));
+        points.push(new OpenLayers.Geometry.Point(x, y));
+    }
+    var ring = new OpenLayers.Geometry.LinearRing(points);
+    return new OpenLayers.Geometry.Polygon([ring]);
+};
+/* ======================================================================
+    OpenLayers/Handler/Point.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler.js
+ * @requires OpenLayers/Geometry/Point.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Point
+ * Handler to draw a point on the map.  Point is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.  The handler triggers
+ * callbacks for 'done' and 'cancel'.  Create a new instance with the
+ * <OpenLayers.Handler.Point> constructor.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Point = OpenLayers.Class(OpenLayers.Handler, {
+    
+    /**
+     * Property: point
+     * {<OpenLayers.Feature.Vector>} The currently drawn point
+     */
+    point: null,
+
+    /**
+     * Property: layer
+     * {<OpenLayers.Layer.Vector>} The temporary drawing layer
+     */
+    layer: null,
+    
+    /**
+     * Property: drawing 
+     * {Boolean} A point is being drawn
+     */
+    drawing: false,
+    
+    /**
+     * Property: mouseDown
+     * {Boolean} The mouse is down
+     */
+    mouseDown: false,
+
+    /**
+     * Property: lastDown
+     * {<OpenLayers.Pixel>} Location of the last mouse down
+     */
+    lastDown: null,
+
+    /**
+     * Property: lastUp
+     * {<OpenLayers.Pixel>}
+     */
+    lastUp: null,
+
+    /**
+     * Constructor: OpenLayers.Handler.Point
+     * Create a new point handler.
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} The control that owns this handler
+     * callbacks - {Object} An object with a 'done' property whose value is a
+     *             function to be called when the point drawing is finished.
+     *             The callback should expect to recieve a single argument,
+     *             the point geometry.  If the callbacks object contains a
+     *             'cancel' property, this function will be called when the
+     *             handler is deactivated while drawing.  The cancel should
+     *             expect to receive a geometry.
+     * options - {Object} An optional object with properties to be set on the
+     *           handler
+     */
+    initialize: function(control, callbacks, options) {
+        // TBD: deal with style
+        this.style = OpenLayers.Util.extend(OpenLayers.Feature.Vector.style['default'], {});
+
+        OpenLayers.Handler.prototype.initialize.apply(this, arguments);
+    },
+    
+    /**
+     * APIMethod: activate
+     * turn on the handler
+     */
+    activate: function() {
+        if(!OpenLayers.Handler.prototype.activate.apply(this, arguments)) {
+            return false;
+        }
+        // create temporary vector layer for rendering geometry sketch
+        // TBD: this could be moved to initialize/destroy - setting visibility here
+        var options = {
+            displayInLayerSwitcher: false,
+            // indicate that the temp vector layer will never be out of range
+            // without this, resolution properties must be specified at the
+            // map-level for this temporary layer to init its resolutions
+            // correctly
+            calculateInRange: function() { return true; }
+        };
+        this.layer = new OpenLayers.Layer.Vector(this.CLASS_NAME, options);
+        this.map.addLayer(this.layer);
+        return true;
+    },
+    
+    /**
+     * Method: createFeature
+     * Add temporary features
+     */
+    createFeature: function() {
+        this.point = new OpenLayers.Feature.Vector(
+                                              new OpenLayers.Geometry.Point());
+    },
+
+    /**
+     * APIMethod: deactivate
+     * turn off the handler
+     */
+    deactivate: function() {
+        if(!OpenLayers.Handler.prototype.deactivate.apply(this, arguments)) {
+            return false;
+        }
+        // call the cancel callback if mid-drawing
+        if(this.drawing) {
+            this.cancel();
+        }
+        // If a layer's map property is set to null, it means that that layer
+        // isn't added to the map. Since we ourself added the layer to the map
+        // in activate(), we can assume that if this.layer.map is null it means
+        // that the layer has been destroyed (as a result of map.destroy() for
+        // example.
+        if (this.layer.map != null) {
+            this.layer.destroy(false);
+        }
+        this.layer = null;
+        return true;
+    },
+    
+    /**
+     * Method: destroyFeature
+     * Destroy the temporary geometries
+     */
+    destroyFeature: function() {
+        if(this.point) {
+            this.point.destroy();
+        }
+        this.point = null;
+    },
+
+    /**
+     * Method: finalize
+     * Finish the geometry and call the "done" callback.
+     */
+    finalize: function() {
+        this.layer.renderer.clear();
+        this.drawing = false;
+        this.mouseDown = false;
+        this.lastDown = null;
+        this.lastUp = null;
+        this.callback("done", [this.geometryClone()]);
+        this.destroyFeature();
+    },
+
+    /**
+     * APIMethod: cancel
+     * Finish the geometry and call the "cancel" callback.
+     */
+    cancel: function() {
+        this.layer.renderer.clear();
+        this.drawing = false;
+        this.mouseDown = false;
+        this.lastDown = null;
+        this.lastUp = null;
+        this.callback("cancel", [this.geometryClone()]);
+        this.destroyFeature();
+    },
+
+    /**
+     * Method: click
+     * Handle clicks.  Clicks are stopped from propagating to other listeners
+     *     on map.events or other dom elements.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    click: function(evt) {
+        OpenLayers.Event.stop(evt);
+        return false;
+    },
+
+    /**
+     * Method: dblclick
+     * Handle double-clicks.  Double-clicks are stopped from propagating to other
+     *     listeners on map.events or other dom elements.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    dblclick: function(evt) {
+        OpenLayers.Event.stop(evt);
+        return false;
+    },
+    
+    /**
+     * Method: drawFeature
+     * Render features on the temporary layer.
+     */
+    drawFeature: function() {
+        this.layer.drawFeature(this.point, this.style);
+    },
+    
+    /**
+     * Method: geometryClone
+     * Return a clone of the relevant geometry.
+     *
+     * Returns:
+     * {<OpenLayers.Geometry.Point>}
+     */
+    geometryClone: function() {
+        return this.point.geometry.clone();
+    },
+  
+    /**
+     * Method: mousedown
+     * Handle mouse down.  Adjust the geometry and redraw.
+     * Return determines whether to propagate the event on the map.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mousedown: function(evt) {
+        // check keyboard modifiers
+        if(!this.checkModifiers(evt)) {
+            return true;
+        }
+        // ignore double-clicks
+        if(this.lastDown && this.lastDown.equals(evt.xy)) {
+            return true;
+        }
+        if(this.lastDown == null) {
+            this.createFeature();
+        }
+        this.lastDown = evt.xy;
+        this.drawing = true;
+        var lonlat = this.map.getLonLatFromPixel(evt.xy);
+        this.point.geometry.x = lonlat.lon;
+        this.point.geometry.y = lonlat.lat;
+        this.drawFeature();
+        return false;
+    },
+
+    /**
+     * Method: mousemove
+     * Handle mouse move.  Adjust the geometry and redraw.
+     * Return determines whether to propagate the event on the map.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mousemove: function (evt) {
+        if(this.drawing) {
+            var lonlat = this.map.getLonLatFromPixel(evt.xy);
+            this.point.geometry.x = lonlat.lon;
+            this.point.geometry.y = lonlat.lat;
+            this.point.geometry.clearBounds();
+            this.drawFeature();
+        }
+        return true;
+    },
+
+    /**
+     * Method: mouseup
+     * Handle mouse up.  Send the latest point in the geometry to the control.
+     * Return determines whether to propagate the event on the map.
+     *
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mouseup: function (evt) {
+        if(this.drawing) {
+            this.finalize();
+            return false;
+        } else {
+            return true;
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Point"
+});
+/* ======================================================================
+    OpenLayers/Layer/GML.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.GML
+ * Create a vector layer by parsing a GML file. The GML file is
+ *     passed in as a parameter.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.Vector>
+ */
+OpenLayers.Layer.GML = OpenLayers.Class(OpenLayers.Layer.Vector, {
+    
+    /**
+      * Property: loaded
+      * {Boolean} Flag for whether the GML data has been loaded yet.
+      */
+    loaded: false,
+
+    /**
+      * APIProperty: format
+      * {<OpenLayers.Format>} The format you want the data to be parsed with.
+      */
+    format: null,
+
+    /**
+     * APIProperty: formatOptions
+     * {Object} Hash of options which should be passed to the format when it is
+     * created. Must be passed in the constructor.
+     */
+    formatOptions: null, 
+    
+    /**
+     * Constructor: OpenLayers.Layer.GML
+     * Load and parse a single file on the web, according to the format
+     * provided via the 'format' option, defaulting to GML. 
+     *
+     * Parameters:
+     * name - {String} 
+     * url - {String} URL of a GML file.
+     * options - {Object} Hashtable of extra options to tag onto the layer.
+     */
+     initialize: function(name, url, options) {
+        var newArguments = [];
+        newArguments.push(name, options);
+        OpenLayers.Layer.Vector.prototype.initialize.apply(this, newArguments);
+        this.url = url;
+    },
+
+    /**
+     * APIMethod: setVisibility
+     * Set the visibility flag for the layer and hide/show&redraw accordingly. 
+     * Fire event unless otherwise specified
+     * GML will be loaded if the layer is being made visible for the first
+     * time.
+     *  
+     * Parameters:
+     * visible - {Boolean} Whether or not to display the layer 
+     *                          (if in range)
+     * noEvent - {Boolean} 
+     */
+    setVisibility: function(visibility, noEvent) {
+        OpenLayers.Layer.Vector.prototype.setVisibility.apply(this, arguments);
+        if(this.visibility && !this.loaded){
+            // Load the GML
+            this.loadGML();
+        }
+    },
+
+    /**
+     * Method: moveTo
+     * If layer is visible and GML has not been loaded, load GML, then load GML
+     * and call OpenLayers.Layer.Vector.moveTo() to redraw at the new location.
+     * 
+     * Parameters:
+     * bounds - {Object} 
+     * zoomChanged - {Object} 
+     * minor - {Object} 
+     */
+    moveTo:function(bounds, zoomChanged, minor) {
+        OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments);
+        // Wait until initialisation is complete before loading GML
+        // otherwise we can get a race condition where the root HTML DOM is
+        // loaded after the GML is paited.
+        // See http://trac.openlayers.org/ticket/404
+        if(this.visibility && !this.loaded){
+            this.events.triggerEvent("loadstart");
+            this.loadGML();
+        }
+    },
+
+    /**
+     * Method: loadGML
+     */
+    loadGML: function() {
+        if (!this.loaded) {
+            OpenLayers.Request.GET({
+                url: this.url,
+                success: this.requestSuccess,
+                failure: this.requestFailure,
+                scope: this
+            });
+            this.loaded = true;
+        }    
+    },    
+    
+    /**
+     * Method: setUrl
+     * Change the URL and reload the GML
+     *
+     * Parameters:
+     * url - {String} URL of a GML file.
+     */
+    setUrl:function(url) {
+        this.url = url;
+        this.destroyFeatures();
+        this.loaded = false;
+        this.events.triggerEvent("loadstart");
+        this.loadGML();
+    },
+    
+    /**
+     * Method: requestSuccess
+     * Process GML after it has been loaded.
+     * Called by initialise() and loadUrl() after the GML has been loaded.
+     *
+     * Parameters:
+     * request - {String} 
+     */
+    requestSuccess:function(request) {
+        var doc = request.responseXML;
+        
+        if (!doc || !doc.documentElement) {
+            doc = request.responseText;
+        }
+        
+        var options = {};
+        
+        OpenLayers.Util.extend(options, this.formatOptions);
+        if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+            options.externalProjection = this.projection;
+            options.internalProjection = this.map.getProjectionObject();
+        }    
+        
+        var gml = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
+        this.addFeatures(gml.read(doc));
+        this.events.triggerEvent("loadend");
+    },
+    
+    /**
+     * Method: requestFailure
+     * Process a failed loading of GML.
+     * Called by initialise() and loadUrl() if there was a problem loading GML.
+     *
+     * Parameters:
+     * request - {String} 
+     */
+    requestFailure: function(request) {
+        OpenLayers.Console.userError(OpenLayers.i18n("errorLoadingGML", {'url':this.url}));
+        this.events.triggerEvent("loadend");
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.GML"
+});
+/* ======================================================================
+    OpenLayers/Layer/PointTrack.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2007 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Layer/Vector.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.PointTrack
+ * Vector layer to display ordered point features as a line, creating one
+ * LineString feature for each pair of two points.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Layer.Vector> 
+ */
+OpenLayers.Layer.PointTrack = OpenLayers.Class(OpenLayers.Layer.Vector, {
+  
+    /**
+     * APIProperty:
+     * dataFrom  - {<OpenLayers.Layer.PointTrack.dataFrom>} optional. If the
+     *             lines should get the data/attributes from one of the two
+     *             points, creating it, which one should it be?
+     */
+    dataFrom: null,
+    
+    /**
+     * Constructor: OpenLayers.PointTrack
+     * Constructor for a new OpenLayers.PointTrack instance.
+     *
+     * Parameters:
+     * name     - {String} name of the layer
+     * options  - {Object} Optional object with properties to tag onto the
+     *            instance.
+     */    
+    initialize: function(name, options) {
+        OpenLayers.Layer.Vector.prototype.initialize.apply(this, arguments);
+    },
+        
+    /**
+     * APIMethod: addNodes
+     * Adds point features that will be used to create lines from, using point
+     * pairs. The first point of a pair will be the source node, the second
+     * will be the target node.
+     * 
+     * Parameters:
+     * pointFeatures - {Array(<OpenLayers.Feature>)}
+     * 
+     */
+    addNodes: function(pointFeatures) {
+        if (pointFeatures.length < 2) {
+            OpenLayers.Console.error(
+                    "At least two point features have to be added to create" +
+                    "a line from");
+            return;
+        }
+        
+        var lines = new Array(pointFeatures.length-1);
+        
+        var pointFeature, startPoint, endPoint;
+        for(var i=0, len=pointFeatures.length; i<len; i++) {
+            pointFeature = pointFeatures[i];
+            endPoint = pointFeature.geometry;
+            
+            if (!endPoint) {
+              var lonlat = pointFeature.lonlat;
+              endPoint = new OpenLayers.Geometry.Point(lonlat.lon, lonlat.lat);
+            } else if(endPoint.CLASS_NAME != "OpenLayers.Geometry.Point") {
+                OpenLayers.Console.error(
+                        "Only features with point geometries are supported.");
+                return;
+            }
+            
+            if(i > 0) {
+                var attributes = (this.dataFrom != null) ?
+                        (pointFeatures[i+this.dataFrom].data ||
+                                pointFeatures[i+this.dataFrom].attributes) :
+                        null;
+                var line = new OpenLayers.Geometry.LineString([startPoint,
+                        endPoint]);
+                        
+                lines[i-1] = new OpenLayers.Feature.Vector(line, attributes);
+            }
+            
+            startPoint = endPoint;
+        }
+
+        this.addFeatures(lines);
+    },
+    
+    CLASS_NAME: "OpenLayers.Layer.PointTrack"
+});
+
+/**
+ * Constant: OpenLayers.Layer.PointTrack.dataFrom
+ * {Object} with the following keys
+ * - SOURCE_NODE: take data/attributes from the source node of the line
+ * - TARGET_NODE: take data/attributes from the target node of the line
+ */
+OpenLayers.Layer.PointTrack.dataFrom = {'SOURCE_NODE': -1, 'TARGET_NODE': 0};
+
+/* ======================================================================
+    OpenLayers/Layer/WFS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Tile/WFS.js
+ * @requires OpenLayers/Layer/Vector.js
+ * @requires OpenLayers/Layer/Markers.js
+ */
+
+/**
+ * Class: OpenLayers.Layer.WFS
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Layer.Vector>
+ *  - <OpenLayers.Layer.Markers>
+ */
+OpenLayers.Layer.WFS = OpenLayers.Class(
+  OpenLayers.Layer.Vector, OpenLayers.Layer.Markers, {
+
+    /**
+     * APIProperty: isBaseLayer
+     * {Boolean} WFS layer is not a base layer by default. 
+     */
+    isBaseLayer: false,
+    
+    /**
+     * Property: tile
+     * {<OpenLayers.Tile.WFS>}
+     */
+    tile: null,    
+    
+    /**
+     * APIProperty: ratio
+     * {Float} the ratio of image/tile size to map size (this is the untiled
+     *     buffer)
+     */
+    ratio: 2,
+
+    /**  
+     * Property: DEFAULT_PARAMS
+     * {Object} Hashtable of default key/value parameters
+     */
+    DEFAULT_PARAMS: { service: "WFS",
+                      version: "1.0.0",
+                      request: "GetFeature"
+                    },
+    
+    /** 
+     * APIProperty: featureClass
+     * {<OpenLayers.Feature>} If featureClass is defined, an old-style markers
+     *     based WFS layer is created instead of a new-style vector layer. If
+     *     sent, this should be a subclass of OpenLayers.Feature
+     */
+    featureClass: null,
+    
+    /**
+      * APIProperty: format
+      * {<OpenLayers.Format>} The format you want the data to be parsed with.
+      * Must be passed in the constructor. Should be a class, not an instance.
+      * This option can only be used if no featureClass is passed / vectorMode
+      * is false: if a featureClass is passed, then this parameter is ignored.
+      */
+    format: null,
+
+    /** 
+     * Property: formatObject
+     * {<OpenLayers.Format>} Internally created/managed format object, used by
+     * the Tile to parse data.
+     */
+    formatObject: null,
+
+    /**
+     * APIProperty: formatOptions
+     * {Object} Hash of options which should be passed to the format when it is
+     * created. Must be passed in the constructor.
+     */
+    formatOptions: null, 
+
+    /**
+     * Property: vectorMode
+     * {Boolean} Should be calculated automatically. Determines whether the
+     *     layer is in vector mode or marker mode.
+     */
+    vectorMode: true, 
+    
+    /**
+     * APIProperty: encodeBBOX
+     * {Boolean} Should the BBOX commas be encoded? The WMS spec says 'no', 
+     *     but some services want it that way. Default false.
+     */
+    encodeBBOX: false,
+    
+    /**
+     * APIProperty: extractAttributes 
+     * {Boolean} Should the WFS layer parse attributes from the retrieved
+     *     GML? Defaults to false. If enabled, parsing is slower, but 
+     *     attributes are available in the attributes property of 
+     *     layer features.
+     */
+    extractAttributes: false,
+
+    /**
+     * Constructor: OpenLayers.Layer.WFS
+     *
+     * Parameters:
+     * name - {String} 
+     * url - {String} 
+     * params - {Object} 
+     * options - {Object} Hashtable of extra options to tag onto the layer
+     */
+    initialize: function(name, url, params, options) {
+        if (options == undefined) { options = {}; } 
+        
+        if (options.featureClass || 
+            !OpenLayers.Layer.Vector || 
+            !OpenLayers.Feature.Vector) {
+            this.vectorMode = false;
+        }    
+        
+        // Turn off error reporting, browsers like Safari may work
+        // depending on the setup, and we don't want an unneccesary alert.
+        OpenLayers.Util.extend(options, {'reportError': false});
+        var newArguments = [];
+        newArguments.push(name, options);
+        OpenLayers.Layer.Vector.prototype.initialize.apply(this, newArguments);
+        if (!this.renderer || !this.vectorMode) {
+            this.vectorMode = false; 
+            if (!options.featureClass) {
+                options.featureClass = OpenLayers.Feature.WFS;
+            }   
+            OpenLayers.Layer.Markers.prototype.initialize.apply(this, 
+                                                                newArguments);
+        }
+        
+        if (this.params && this.params.typename && !this.options.typename) {
+            this.options.typename = this.params.typename;
+        }
+        
+        if (!this.options.geometry_column) {
+            this.options.geometry_column = "the_geom";
+        }    
+        
+        this.params = OpenLayers.Util.applyDefaults(
+            params, 
+            OpenLayers.Util.upperCaseObject(this.DEFAULT_PARAMS)
+        );
+        this.url = url;
+    },    
+    
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function() {
+        if (this.vectorMode) {
+            OpenLayers.Layer.Vector.prototype.destroy.apply(this, arguments);
+        } else {    
+            OpenLayers.Layer.Markers.prototype.destroy.apply(this, arguments);
+        }    
+        if (this.tile) {
+            this.tile.destroy();
+        }
+        this.tile = null;
+
+        this.ratio = null;
+        this.featureClass = null;
+        this.format = null;
+
+        if (this.formatObject && this.formatObject.destroy) {
+            this.formatObject.destroy();
+        }
+        this.formatObject = null;
+        
+        this.formatOptions = null;
+        this.vectorMode = null;
+        this.encodeBBOX = null;
+        this.extractAttributes = null;
+    },
+    
+    /**
+     * Method: setMap
+     * 
+     * Parameters:
+     * map - {<OpenLayers.Map>} 
+     */
+    setMap: function(map) {
+        if (this.vectorMode) {
+            OpenLayers.Layer.Vector.prototype.setMap.apply(this, arguments);
+            
+            var options = {
+              'extractAttributes': this.extractAttributes
+            };
+            
+            OpenLayers.Util.extend(options, this.formatOptions);
+            if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+                options.externalProjection = this.projection;
+                options.internalProjection = this.map.getProjectionObject();
+            }    
+            
+            this.formatObject = this.format ? new this.format(options) : new OpenLayers.Format.GML(options);
+        } else {    
+            OpenLayers.Layer.Markers.prototype.setMap.apply(this, arguments);
+        }    
+    },
+    
+    /** 
+     * Method: moveTo
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} 
+     * zoomChanged - {Boolean} 
+     * dragging - {Boolean} 
+     */
+    moveTo:function(bounds, zoomChanged, dragging) {
+        if (this.vectorMode) {
+            OpenLayers.Layer.Vector.prototype.moveTo.apply(this, arguments);
+        } else {
+            OpenLayers.Layer.Markers.prototype.moveTo.apply(this, arguments);
+        }    
+
+        // don't load wfs features while dragging, wait for drag end
+        if (dragging) {
+            // TBD try to hide the vector layer while dragging
+            // this.setVisibility(false);
+            // this will probably help for panning performances
+            return false;
+        }
+        
+        if ( zoomChanged ) {
+            if (this.vectorMode) {
+                this.renderer.clear();
+            }
+        }
+        
+    //DEPRECATED - REMOVE IN 3.0
+        // don't load data if current zoom level doesn't match
+        if (this.options.minZoomLevel) {
+            OpenLayers.Console.warn(OpenLayers.i18n('minZoomLevelError'));
+            
+            if (this.map.getZoom() < this.options.minZoomLevel) {
+                return null;
+            }
+        }
+        
+        if (bounds == null) {
+            bounds = this.map.getExtent();
+        }
+
+        var firstRendering = (this.tile == null);
+
+        //does the new bounds to which we need to move fall outside of the 
+        // current tile's bounds?
+        var outOfBounds = (!firstRendering &&
+                           !this.tile.bounds.containsBounds(bounds));
+
+        if (zoomChanged || firstRendering || (!dragging && outOfBounds)) {
+            //determine new tile bounds
+            var center = bounds.getCenterLonLat();
+            var tileWidth = bounds.getWidth() * this.ratio;
+            var tileHeight = bounds.getHeight() * this.ratio;
+            var tileBounds = 
+                new OpenLayers.Bounds(center.lon - (tileWidth / 2),
+                                      center.lat - (tileHeight / 2),
+                                      center.lon + (tileWidth / 2),
+                                      center.lat + (tileHeight / 2));
+
+            //determine new tile size
+            var tileSize = this.map.getSize();
+            tileSize.w = tileSize.w * this.ratio;
+            tileSize.h = tileSize.h * this.ratio;
+
+            //determine new position (upper left corner of new bounds)
+            var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top);
+            var pos = this.map.getLayerPxFromLonLat(ul);
+
+            //formulate request url string
+            var url = this.getFullRequestString();
+        
+            var params = {BBOX: this.encodeBBOX ? tileBounds.toBBOX() 
+                                                : tileBounds.toArray()};
+            
+            if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+                var projectedBounds = tileBounds.clone();
+                projectedBounds.transform(this.map.getProjectionObject(), 
+                                          this.projection);
+                params.BBOX = this.encodeBBOX ? projectedBounds.toBBOX() 
+                                              : projectedBounds.toArray();
+            }                                  
+
+            url += "&" + OpenLayers.Util.getParameterString(params);
+
+            if (!this.tile) {
+                this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds, 
+                                                     url, tileSize);
+                this.addTileMonitoringHooks(this.tile);
+                this.tile.draw();
+            } else {
+                if (this.vectorMode) {
+                    this.destroyFeatures();
+                    this.renderer.clear();
+                } else {
+                    this.clearMarkers();
+                }    
+                this.removeTileMonitoringHooks(this.tile);
+                this.tile.destroy();
+                
+                this.tile = null;
+                this.tile = new OpenLayers.Tile.WFS(this, pos, tileBounds, 
+                                                     url, tileSize);
+                this.addTileMonitoringHooks(this.tile);
+                this.tile.draw();
+            } 
+        }
+    },
+
+    /** 
+     * Method: addTileMonitoringHooks
+     * This function takes a tile as input and adds the appropriate hooks to 
+     *     the tile so that the layer can keep track of the loading tile
+     *     (making sure to check that the tile is always the layer's current
+     *     tile before taking any action).
+     * 
+     * Parameters: 
+     * tile - {<OpenLayers.Tile>}
+     */
+    addTileMonitoringHooks: function(tile) {
+        tile.onLoadStart = function() {
+            //if this is the the layer's current tile, then trigger 
+            // a 'loadstart'
+            if (this == this.layer.tile) {
+                this.layer.events.triggerEvent("loadstart");
+            }
+        };
+        tile.events.register("loadstart", tile, tile.onLoadStart);
+      
+        tile.onLoadEnd = function() {
+            //if this is the the layer's current tile, then trigger 
+            // a 'tileloaded' and 'loadend'
+            if (this == this.layer.tile) {
+                this.layer.events.triggerEvent("tileloaded");
+                this.layer.events.triggerEvent("loadend");
+            }
+        };
+        tile.events.register("loadend", tile, tile.onLoadEnd);
+        tile.events.register("unload", tile, tile.onLoadEnd);
+    },
+    
+    /** 
+     * Method: removeTileMonitoringHooks
+     * This function takes a tile as input and removes the tile hooks 
+     *     that were added in addTileMonitoringHooks()
+     * 
+     * Parameters: 
+     * tile - {<OpenLayers.Tile>}
+     */
+    removeTileMonitoringHooks: function(tile) {
+        tile.unload();
+        tile.events.un({
+            "loadstart": tile.onLoadStart,
+            "loadend": tile.onLoadEnd,
+            "unload": tile.onLoadEnd,
+            scope: tile
+        });
+    },
+
+    /**
+     * Method: onMapResize
+     * Call the onMapResize method of the appropriate parent class. 
+     */
+    onMapResize: function() {
+        if(this.vectorMode) {
+            OpenLayers.Layer.Vector.prototype.onMapResize.apply(this, 
+                                                                arguments);
+        } else {
+            OpenLayers.Layer.Markers.prototype.onMapResize.apply(this, 
+                                                                 arguments);
+        }
+    },
+    
+    /**
+     * APIMethod: mergeNewParams
+     * Modify parameters for the layer and redraw.
+     * 
+     * Parameters:
+     * newParams - {Object}
+     */
+    mergeNewParams:function(newParams) {
+        var upperParams = OpenLayers.Util.upperCaseObject(newParams);
+        var newArguments = [upperParams];
+        return OpenLayers.Layer.HTTPRequest.prototype.mergeNewParams.apply(this, 
+                                                                 newArguments);
+    },
+
+    /**
+     * APIMethod: clone
+     *
+     * Parameters:
+     * obj - {Object} 
+     * 
+     * Returns:
+     * {<OpenLayers.Layer.WFS>} An exact clone of this OpenLayers.Layer.WFS
+     */
+    clone: function (obj) {
+        
+        if (obj == null) {
+            obj = new OpenLayers.Layer.WFS(this.name,
+                                           this.url,
+                                           this.params,
+                                           this.options);
+        }
+
+        //get all additions from superclasses
+        if (this.vectorMode) {
+            obj = OpenLayers.Layer.Vector.prototype.clone.apply(this, [obj]);
+        } else {
+            obj = OpenLayers.Layer.Markers.prototype.clone.apply(this, [obj]);
+        }    
+
+        // copy/set any non-init, non-simple values here
+
+        return obj;
+    },
+
+    /** 
+     * APIMethod: getFullRequestString
+     * combine the layer's url with its params and these newParams. 
+     *   
+     *    Add the SRS parameter from 'projection' -- this is probably
+     *     more eloquently done via a setProjection() method, but this 
+     *     works for now and always.
+     *
+     * Parameters:
+     * newParams - {Object} 
+     * altUrl - {String} Use this as the url instead of the layer's url
+     */
+    getFullRequestString:function(newParams, altUrl) {
+        var projectionCode = this.projection.getCode() || this.map.getProjection();
+        this.params.SRS = (projectionCode == "none") ? null : projectionCode;
+
+        return OpenLayers.Layer.Grid.prototype.getFullRequestString.apply(
+                                                    this, arguments);
+    },
+   
+    /**
+     * APIMethod: commit
+     * Write out the data to a WFS server.
+     */
+    commit: function() {
+        if (!this.writer) {
+            var options = {};
+            if (this.map && !this.projection.equals(this.map.getProjectionObject())) {
+                options.externalProjection = this.projection;
+                options.internalProjection = this.map.getProjectionObject();
+            }    
+            
+            this.writer = new OpenLayers.Format.WFS(options,this);
+        }
+
+        var data = this.writer.write(this.features);
+
+        OpenLayers.Request.POST({
+            url: this.url,
+            data: data,
+            success: this.commitSuccess,
+            failure: this.commitFailure,
+            scope: this
+        });
+    },
+
+    /**
+     * Method: commitSuccess
+     * Called when the Ajax request returns a response
+     *
+     * Parameters:
+     * response - {XmlNode} from server
+     */
+    commitSuccess: function(request) {
+        var response = request.responseText;
+        if (response.indexOf('SUCCESS') != -1) {
+            this.commitReport(OpenLayers.i18n("commitSuccess", {'response':response}));
+            
+            for(var i = 0; i < this.features.length; i++) {
+                this.features[i].state = null;
+            }    
+            // TBD redraw the layer or reset the state of features
+            // foreach features: set state to null
+        } else if (response.indexOf('FAILED') != -1 ||
+            response.indexOf('Exception') != -1) {
+            this.commitReport(OpenLayers.i18n("commitFailed", {'response':response}));
+        }
+    },
+    
+    /**
+     * Method: commitFailure
+     * Called when the Ajax request fails
+     *
+     * Parameters:
+     * response - {XmlNode} from server
+     */
+    commitFailure: function(request) {},
+    
+    /**
+     * APIMethod: commitReport 
+     * Called with a 'success' message if the commit succeeded, otherwise
+     *     a failure message, and the full request text as a second parameter.
+     *     Override this function to provide custom transaction reporting.
+     *
+     * string - {String} reporting string
+     * response - {String} full XML response
+     */
+    commitReport: function(string, response) {
+        OpenLayers.Console.userError(string);
+    },
+
+    
+    /**
+     * APIMethod: refresh
+     * Refreshes all the features of the layer
+     */
+    refresh: function() {
+        if (this.tile) {
+            if (this.vectorMode) {
+                this.renderer.clear();
+                this.features.length = 0;
+            } else {   
+                this.clearMarkers();
+                this.markers.length = 0;
+            }    
+            this.tile.draw();
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Layer.WFS"
+});
+/* ======================================================================
+    OpenLayers/Format/Filter/v1.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/Filter.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1
+ * Superclass for Filter version 1 parsers.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.Filter.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * Property: namespaces
+     * {Object} Mapping of namespace aliases to namespace URIs.
+     */
+    namespaces: {
+        ogc: "http://www.opengis.net/ogc",
+        xlink: "http://www.w3.org/1999/xlink",
+        xsi: "http://www.w3.org/2001/XMLSchema-instance"
+    },
+    
+    /**
+     * Property: defaultPrefix
+     */
+    defaultPrefix: "ogc",
+
+    /**
+     * Property: schemaLocation
+     * {String} Schema location for a particular minor version.
+     */
+    schemaLocation: null,
+    
+    /**
+     * Constructor: OpenLayers.Format.Filter.v1
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.Filter> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+    
+    /**
+     * Method: read
+     *
+     * Parameters:
+     * data - {DOMElement} A Filter document element.
+     *
+     * Returns:
+     * {<OpenLayers.Filter>} A filter object.
+     */
+    read: function(data) {
+        var obj = {}
+        var filter = this.readers.ogc["Filter"].apply(this, [data, obj]);
+        return obj.filter;
+    },
+    
+    /**
+     * Property: readers
+     * Contains public functions, grouped by namespace prefix, that will
+     *     be applied when a namespaced node is found matching the function
+     *     name.  The function will be applied in the scope of this parser
+     *     with two arguments: the node being read and a context object passed
+     *     from the parent.
+     */
+    readers: {
+        "ogc": {
+            "Filter": function(node, parent) {
+                // Filters correspond to subclasses of OpenLayers.Filter.
+                // Since they contain information we don't persist, we
+                // create a temporary object and then pass on the filter
+                // (ogc:Filter) to the parent obj.
+                var obj = {
+                    fids: [],
+                    filters: []
+                };
+                this.readChildNodes(node, obj);
+                if(obj.fids.length > 0) {
+                    parent.filter = new OpenLayers.Filter.FeatureId({
+                        fids: obj.fids
+                    });
+                } else if(obj.filters.length > 0) {
+                    parent.filter = obj.filters[0];
+                }
+            },
+            "FeatureId": function(node, obj) {
+                var fid = node.getAttribute("fid");
+                if(fid) {
+                    obj.fids.push(fid);
+                }
+            },
+            "And": function(node, obj) {
+                var filter = new OpenLayers.Filter.Logical({
+                    type: OpenLayers.Filter.Logical.AND
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "Or": function(node, obj) {
+                var filter = new OpenLayers.Filter.Logical({
+                    type: OpenLayers.Filter.Logical.OR
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "Not": function(node, obj) {
+                var filter = new OpenLayers.Filter.Logical({
+                    type: OpenLayers.Filter.Logical.NOT
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsEqualTo": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.EQUAL_TO
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsNotEqualTo": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.NOT_EQUAL_TO
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsLessThan": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.LESS_THAN
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsGreaterThan": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.GREATER_THAN
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsLessThanOrEqualTo": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.LESS_THAN_OR_EQUAL_TO
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsGreaterThanOrEqualTo": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.GREATER_THAN_OR_EQUAL_TO
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsBetween": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.BETWEEN
+                });
+                this.readChildNodes(node, filter);
+                obj.filters.push(filter);
+            },
+            "PropertyIsLike": function(node, obj) {
+                var filter = new OpenLayers.Filter.Comparison({
+                    type: OpenLayers.Filter.Comparison.LIKE
+                });
+                this.readChildNodes(node, filter);
+                var wildCard = node.getAttribute("wildCard");
+                var singleChar = node.getAttribute("singleChar");
+                var esc = node.getAttribute("escape");
+                filter.value2regex(wildCard, singleChar, esc);
+                obj.filters.push(filter);
+            },
+            "Literal": function(node, obj) {
+                obj.value = this.getChildValue(node);
+            },
+            "PropertyName": function(node, filter) {
+                filter.property = this.getChildValue(node);
+            },
+            "LowerBoundary": function(node, filter) {
+                filter.lowerBoundary = this.readOgcExpression(node);
+            },
+            "UpperBoundary": function(node, filter) {
+                filter.upperBoundary = this.readOgcExpression(node);
+            }
+            
+        }
+    },
+    
+    /**
+     * Method: readOgcExpression
+     * Limited support for OGC expressions.
+     *
+     * Parameters:
+     * node - {DOMElement} A DOM element that contains an ogc:expression.
+     *
+     * Returns:
+     * {String} A value to be used in a symbolizer.
+     */
+    readOgcExpression: function(node) {
+        var obj = {};
+        this.readChildNodes(node, obj);
+        var value = obj.value;
+        if(!value) {
+            value = this.getChildValue(node);
+        }
+        return value;
+    },
+
+    /**
+     * Method: write
+     *
+     * Parameters:
+     * filter - {<OpenLayers.Filter>} A filter object.
+     *
+     * Returns:
+     * {DOMElement} An ogc:Filter element.
+     */
+    write: function(filter) {
+        return this.writers.ogc["Filter"].apply(this, [filter]);
+    },
+    
+    /**
+     * Property: writers
+     * As a compliment to the readers property, this structure contains public
+     *     writing functions grouped by namespace alias and named like the
+     *     node names they produce.
+     */
+    writers: {
+        "ogc": {
+            "Filter": function(filter) {
+                var node = this.createElementNSPlus("ogc:Filter");
+                var sub = filter.CLASS_NAME.split(".").pop();
+                if(sub == "FeatureId") {
+                    for(var i=0; i<filter.fids.length; ++i) {
+                        this.writeNode(node, "FeatureId", filter.fids[i]);
+                    }
+                } else {
+                    this.writeNode(node, this.getFilterType(filter), filter);
+                }
+                return node;
+            },
+            "FeatureId": function(fid) {
+                return this.createElementNSPlus("ogc:FeatureId", {
+                    attributes: {fid: fid}
+                });
+            },
+            "And": function(filter) {
+                var node = this.createElementNSPlus("ogc:And");
+                var childFilter;
+                for(var i=0; i<filter.filters.length; ++i) {
+                    childFilter = filter.filters[i];
+                    this.writeNode(
+                        node, this.getFilterType(childFilter), childFilter
+                    );
+                }
+                return node;
+            },
+            "Or": function(filter) {
+                var node = this.createElementNSPlus("ogc:Or");
+                var childFilter;
+                for(var i=0; i<filter.filters.length; ++i) {
+                    childFilter = filter.filters[i];
+                    this.writeNode(
+                        node, this.getFilterType(childFilter), childFilter
+                    );
+                }
+                return node;
+            },
+            "Not": function(filter) {
+                var node = this.createElementNSPlus("ogc:Not");
+                var childFilter = filter.filters[0];
+                this.writeNode(
+                    node, this.getFilterType(childFilter), childFilter
+                );
+                return node;
+            },
+            "PropertyIsEqualTo": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsEqualTo");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);
+                return node;
+            },
+            "PropertyIsNotEqualTo": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsNotEqualTo");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);
+                return node;
+            },
+            "PropertyIsLessThan": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsLessThan");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);                
+                return node;
+            },
+            "PropertyIsGreaterThan": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsGreaterThan");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);
+                return node;
+            },
+            "PropertyIsLessThanOrEqualTo": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsLessThanOrEqualTo");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);
+                return node;
+            },
+            "PropertyIsGreaterThanOrEqualTo": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsGreaterThanOrEqualTo");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "Literal", filter.value);
+                return node;
+            },
+            "PropertyIsBetween": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsBetween");
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                this.writeNode(node, "LowerBoundary", filter);
+                this.writeNode(node, "UpperBoundary", filter);
+                return node;
+            },
+            "PropertyIsLike": function(filter) {
+                var node = this.createElementNSPlus("ogc:PropertyIsLike", {
+                    attributes: {
+                        wildCard: "*", singleChar: ".", escape: "!"
+                    }
+                });
+                // no ogc:expression handling for now
+                this.writeNode(node, "PropertyName", filter);
+                // convert regex string to ogc string
+                this.writeNode(node, "Literal", filter.regex2value());
+                return node;
+            },
+            "PropertyName": function(filter) {
+                // no ogc:expression handling for now
+                return this.createElementNSPlus("ogc:PropertyName", {
+                    value: filter.property
+                });
+            },
+            "Literal": function(value) {
+                // no ogc:expression handling for now
+                return this.createElementNSPlus("ogc:Literal", {
+                    value: value
+                });
+            },
+            "LowerBoundary": function(filter) {
+                // no ogc:expression handling for now
+                var node = this.createElementNSPlus("ogc:LowerBoundary");
+                this.writeNode(node, "Literal", filter.lowerBoundary);
+                return node;
+            },
+            "UpperBoundary": function(filter) {
+                // no ogc:expression handling for now
+                var node = this.createElementNSPlus("ogc:UpperBoundary");
+                this.writeNode(node, "Literal", filter.upperBoundary);
+                return node;
+            },
+            "BBOX": function(filter) {
+                var node = this.createElementNSPlus("ogc:BBOX");
+                this.writeNode(node, "PropertyName", filter);
+                var gml = new OpenLayers.Format.GML();
+                node.appendChild(gml.buildGeometryNode(filter.value)); 
+                return node;
+            },
+            "DWITHIN": function(filter) {
+                var node = this.createElementNSPlus("ogc:DWithin");
+                this.writeNode(node, "PropertyName", filter);
+                var gml = new OpenLayers.Format.GML();
+                node.appendChild(gml.buildGeometryNode(filter.value));
+                this.writeNode(node, "Distance", filter);
+                return node;
+            },
+           "INTERSECTS": function(filter) {
+               var node = this.createElementNSPlus("ogc:Intersects");
+               this.writeNode(node, "PropertyName", filter);
+               var gml = new OpenLayers.Format.GML();
+               node.appendChild(gml.buildGeometryNode(filter.value));
+               return node;
+           },
+           "Distance": function(filter) {
+               return this.createElementNSPlus("ogc:Distance", 
+                   {attributes: {units: filter.distanceUnits}, 
+                    value: filter.distance});
+           }
+        }
+    },
+    
+    /**
+     * Method: getFilterType
+     */
+    getFilterType: function(filter) {
+        var filterType = this.filterMap[filter.type];
+        if(!filterType) {
+            throw "Filter writing not supported for rule type: " + filter.type;
+        }
+        return filterType;
+    },
+    
+    /**
+     * Property: filterMap
+     * {Object} Contains a member for each filter type.  Values are node names
+     *     for corresponding OGC Filter child elements.
+     */
+    filterMap: {
+        "&&": "And",
+        "||": "Or",
+        "!": "Not",
+        "==": "PropertyIsEqualTo",
+        "!=": "PropertyIsNotEqualTo",
+        "<": "PropertyIsLessThan",
+        ">": "PropertyIsGreaterThan",
+        "<=": "PropertyIsLessThanOrEqualTo",
+        ">=": "PropertyIsGreaterThanOrEqualTo",
+        "..": "PropertyIsBetween",
+        "~": "PropertyIsLike",
+        "BBOX": "BBOX",
+        "DWITHIN": "DWITHIN",
+        "INTERSECTS": "INTERSECTS"
+    },
+    
+
+    /**
+     * Methods below this point are of general use for versioned XML parsers.
+     * These are candidates for an abstract class.
+     */
+    
+    /**
+     * Method: getNamespacePrefix
+     * Get the namespace prefix for a given uri from the <namespaces> object.
+     *
+     * Returns:
+     * {String} A namespace prefix or null if none found.
+     */
+    getNamespacePrefix: function(uri) {
+        var prefix = null;
+        if(uri == null) {
+            prefix = this.namespaces[this.defaultPrefix];
+        } else {
+            var gotPrefix = false;
+            for(prefix in this.namespaces) {
+                if(this.namespaces[prefix] == uri) {
+                    gotPrefix = true;
+                    break;
+                }
+            }
+            if(!gotPrefix) {
+                prefix = null;
+            }
+        }
+        return prefix;
+    },
+
+
+    /**
+     * Method: readChildNodes
+     */
+    readChildNodes: function(node, obj) {
+        var children = node.childNodes;
+        var child, group, reader, prefix, local;
+        for(var i=0; i<children.length; ++i) {
+            child = children[i];
+            if(child.nodeType == 1) {
+                prefix = this.getNamespacePrefix(child.namespaceURI);
+                local = child.nodeName.split(":").pop();
+                group = this.readers[prefix];
+                if(group) {
+                    reader = group[local];
+                    if(reader) {
+                        reader.apply(this, [child, obj]);
+                    }
+                }
+            }
+        }
+    },
+
+    /**
+     * Method: writeNode
+     * Shorthand for applying one of the named writers and appending the
+     *     results to a node.  If a qualified name is not provided for the
+     *     second argument (and a local name is used instead), the namespace
+     *     of the parent node will be assumed.
+     *
+     * Parameters:
+     * parent - {DOMElement} Result will be appended to this node.
+     * name - {String} The name of a node to generate.  If a qualified name
+     *     (e.g. "pre:Name") is used, the namespace prefix is assumed to be
+     *     in the <writers> group.  If a local name is used (e.g. "Name") then
+     *     the namespace of the parent is assumed.
+     * obj - {Object} Structure containing data for the writer.
+     *
+     * Returns:
+     * {DOMElement} The child node.
+     */
+    writeNode: function(parent, name, obj) {
+        var prefix, local;
+        var split = name.indexOf(":");
+        if(split > 0) {
+            prefix = name.substring(0, split);
+            local = name.substring(split + 1);
+        } else {
+            prefix = this.getNamespacePrefix(parent.namespaceURI);
+            local = name;
+        }
+        var child = this.writers[prefix][local].apply(this, [obj]);
+        parent.appendChild(child);
+        return child;
+    },
+    
+    /**
+     * Method: createElementNSPlus
+     * Shorthand for creating namespaced elements with optional attributes and
+     *     child text nodes.
+     *
+     * Parameters:
+     * name - {String} The qualified node name.
+     * options - {Object} Optional object for node configuration.
+     *
+     * Returns:
+     * {Element} An element node.
+     */
+    createElementNSPlus: function(name, options) {
+        options = options || {};
+        var loc = name.indexOf(":");
+        // order of prefix preference
+        // 1. in the uri option
+        // 2. in the prefix option
+        // 3. in the qualified name
+        // 4. from the defaultPrefix
+        var uri = options.uri || this.namespaces[options.prefix];
+        if(!uri) {
+            loc = name.indexOf(":");
+            uri = this.namespaces[name.substring(0, loc)];
+        }
+        if(!uri) {
+            uri = this.namespaces[this.defaultPrefix];
+        }
+        var node = this.createElementNS(uri, name);
+        if(options.attributes) {
+            this.setAttributes(node, options.attributes);
+        }
+        if(options.value) {
+            node.appendChild(this.createTextNode(options.value));
+        }
+        return node;
+    },
+    
+    /**
+     * Method: setAttributes
+     * Set multiple attributes given key value pairs from an object.
+     *
+     * Parameters:
+     * node - {Element} An element node.
+     * obj - {Object || Array} An object whose properties represent attribute
+     *     names and values represent attribute values.  If an attribute name
+     *     is a qualified name ("prefix:local"), the prefix will be looked up
+     *     in the parsers {namespaces} object.  If the prefix is found,
+     *     setAttributeNS will be used instead of setAttribute.
+     */
+    setAttributes: function(node, obj) {
+        var value, loc, alias, uri;
+        for(var name in obj) {
+            value = obj[name].toString();
+            // check for qualified attribute name ("prefix:local")
+            uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
+            this.setAttributeNS(node, uri, name, value);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Format.Filter.v1" 
+
+});
+/* ======================================================================
+    OpenLayers/Format/SLD/v1.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Rule.js
+ * @requires OpenLayers/Filter.js
+ * @requires OpenLayers/Filter/FeatureId.js
+ * @requires OpenLayers/Filter/Logical.js
+ * @requires OpenLayers/Filter/Comparison.js
+ * @requires OpenLayers/Format/SLD.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD.v1
+ * Superclass for SLD version 1 parsers.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.SLD.v1 = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * Property: namespaces
+     * {Object} Mapping of namespace aliases to namespace URIs.
+     */
+    namespaces: {
+        sld: "http://www.opengis.net/sld",
+        ogc: "http://www.opengis.net/ogc",
+        xlink: "http://www.w3.org/1999/xlink",
+        xsi: "http://www.w3.org/2001/XMLSchema-instance"
+    },
+    
+    /**
+     * Property: defaultPrefix
+     */
+    defaultPrefix: "sld",
+
+    /**
+     * Property: schemaLocation
+     * {String} Schema location for a particular minor version.
+     */
+    schemaLocation: null,
+
+    /**
+     * APIProperty: defaultSymbolizer.
+     * {Object} A symbolizer with the SLD defaults.
+     */
+    defaultSymbolizer: {
+        fillColor: "#808080",
+        fillOpacity: 1,
+        strokeColor: "#000000",
+        strokeOpacity: 1,
+        strokeWidth: 1,
+        strokeDashstyle: "solid",
+        pointRadius: 3,
+        graphicName: "square"
+    },
+    
+    /**
+     * Constructor: OpenLayers.Format.SLD.v1
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.SLD> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        // extend with ogc:Filter readers and writers
+        this.readers["ogc"] = OpenLayers.Format.Filter.v1.prototype.readers["ogc"];
+        this.writers["ogc"] = OpenLayers.Format.Filter.v1.prototype.writers["ogc"];
+        // extend with custom filter methods that may get changed
+        this.readOgcExpression = OpenLayers.Format.Filter.v1.prototype.readOgcExpression;
+        this.getFilterType = OpenLayers.Format.Filter.v1.prototype.getFilterType;
+        this.filterMap = OpenLayers.Format.Filter.v1.prototype.filterMap;
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+    
+    /**
+     * Method: read
+     *
+     * Parameters:
+     * data - {DOMElement} An SLD document element.
+     *
+     * Returns:
+     * {Object} An object representing the SLD.
+     */
+    read: function(data) {        
+        var sld = {
+            namedLayers: {}
+        };
+        this.readChildNodes(data, sld);
+        return sld;
+    },
+    
+    /**
+     * Property: readers
+     * Contains public functions, grouped by namespace prefix, that will
+     *     be applied when a namespaced node is found matching the function
+     *     name.  The function will be applied in the scope of this parser
+     *     with two arguments: the node being read and a context object passed
+     *     from the parent.
+     */
+    readers: {
+        "sld": {
+            "StyledLayerDescriptor": function(node, sld) {
+                sld.version = node.getAttribute("version");
+                this.readChildNodes(node, sld);
+            },
+            "Name": function(node, obj) {
+                obj.name = this.getChildValue(node);
+            },
+            "Title": function(node, obj) {
+                obj.title = this.getChildValue(node);
+            },
+            "Abstract": function(node, obj) {
+                obj.description = this.getChildValue(node);
+            },
+            "NamedLayer": function(node, sld) {
+                var layer = {
+                    userStyles: [],
+                    namedStyles: []
+                };
+                this.readChildNodes(node, layer);
+                // give each of the user styles this layer name
+                for(var i=0, len=layer.userStyles.length; i<len; ++i) {
+                    layer.userStyles[i].layerName = layer.name;
+                }
+                sld.namedLayers[layer.name] = layer;
+            },
+            "NamedStyle": function(node, layer) {
+                layer.namedStyles.push(
+                    this.getChildName(node.firstChild)
+                );
+            },
+            "UserStyle": function(node, layer) {
+                var style = new OpenLayers.Style(this.defaultSymbolizer);
+                this.readChildNodes(node, style);
+                layer.userStyles.push(style);
+            },
+            "IsDefault": function(node, style) {
+                if(this.getChildValue(node) == "1") {
+                    style.isDefault = true;
+                }
+            },
+            "FeatureTypeStyle": function(node, style) {
+                // OpenLayers doesn't have a place for FeatureTypeStyle
+                // Name, Title, Abstract, FeatureTypeName, or
+                // SemanticTypeIdentifier so, we make a temporary object
+                // and later just use the Rule(s).
+                var obj = {
+                    rules: []
+                };
+                this.readChildNodes(node, obj);
+                style.rules = obj.rules;
+            },
+            "Rule": function(node, obj) {
+                var rule = new OpenLayers.Rule();
+                this.readChildNodes(node, rule);
+                obj.rules.push(rule);
+            },
+            "ElseFilter": function(node, rule) {
+                rule.elseFilter = true;
+            },
+            "MinScaleDenominator": function(node, rule) {
+                rule.minScaleDenominator = this.getChildValue(node);
+            },
+            "MaxScaleDenominator": function(node, rule) {
+                rule.maxScaleDenominator = this.getChildValue(node);
+            },
+            "LineSymbolizer": function(node, rule) {
+                // OpenLayers doens't do painter's order, instead we extend
+                var symbolizer = rule.symbolizer["Line"] || {};
+                this.readChildNodes(node, symbolizer);
+                // in case it didn't exist before
+                rule.symbolizer["Line"] = symbolizer;
+            },
+            "PolygonSymbolizer": function(node, rule) {
+                // OpenLayers doens't do painter's order, instead we extend
+                var symbolizer = rule.symbolizer["Polygon"] || {};
+                this.readChildNodes(node, symbolizer);
+                // in case it didn't exist before
+                rule.symbolizer["Polygon"] = symbolizer;
+            },
+            "PointSymbolizer": function(node, rule) {
+                // OpenLayers doens't do painter's order, instead we extend
+                var symbolizer = rule.symbolizer["Point"] || {};
+                this.readChildNodes(node, symbolizer);
+                // in case it didn't exist before
+                rule.symbolizer["Point"] = symbolizer;
+            },
+            "Stroke": function(node, symbolizer) {
+                this.readChildNodes(node, symbolizer);
+            },
+            "Fill": function(node, symbolizer) {
+                this.readChildNodes(node, symbolizer);
+            },
+            "CssParameter": function(node, symbolizer) {
+                var cssProperty = node.getAttribute("name");
+                var symProperty = this.cssMap[cssProperty];
+                if(symProperty) {
+                    // Limited support for parsing of OGC expressions
+                    var value = this.readOgcExpression(node);
+                    // always string, could be an empty string
+                    if(value) {
+                        symbolizer[symProperty] = value;
+                    }
+                }
+            },
+            "Graphic": function(node, symbolizer) {
+                var graphic = {};
+                // painter's order not respected here, clobber previous with next
+                this.readChildNodes(node, graphic);
+                // directly properties with names that match symbolizer properties
+                var properties = [
+                    "strokeColor", "strokeWidth", "strokeOpacity",
+                    "strokeLinecap", "fillColor", "fillOpacity",
+                    "graphicName", "rotation", "graphicFormat"
+                ];
+                var prop, value;
+                for(var i=0, len=properties.length; i<len; ++i) {
+                    prop = properties[i];
+                    value = graphic[prop];
+                    if(value != undefined) {
+                        symbolizer[prop] = value;
+                    }
+                }
+                // set other generic properties with specific graphic property names
+                if(graphic.opacity != undefined) {
+                    symbolizer.graphicOpacity = graphic.opacity;
+                }
+                if(graphic.size != undefined) {
+                    symbolizer.pointRadius = graphic.size / 2;
+                }
+                if(graphic.href != undefined) {
+                    symbolizer.externalGraphic = graphic.href;
+                }
+                if(graphic.rotation != undefined) {
+                    symbolizer.rotation = graphic.rotation;
+                }
+            },
+            "ExternalGraphic": function(node, graphic) {
+                this.readChildNodes(node, graphic);
+            },
+            "Mark": function(node, graphic) {
+                this.readChildNodes(node, graphic);
+            },
+            "WellKnownName": function(node, graphic) {
+                graphic.graphicName = this.getChildValue(node);
+            },
+            "Opacity": function(node, obj) {
+                // No support for parsing of OGC expressions
+                var opacity = this.getChildValue(node);
+                // always string, could be empty string
+                if(opacity) {
+                    obj.opacity = opacity;
+                }
+            },
+            "Size": function(node, obj) {
+                // No support for parsing of OGC expressions
+                var size = this.getChildValue(node);
+                // always string, could be empty string
+                if(size) {
+                    obj.size = size;
+                }
+            },
+            "Rotation": function(node, obj) {
+                // No support for parsing of OGC expressions
+                var rotation = this.getChildValue(node);
+                // always string, could be empty string
+                if(rotation) {
+                    obj.rotation = rotation;
+                }
+            },
+            "OnlineResource": function(node, obj) {
+                obj.href = this.getAttributeNS(
+                    node, this.namespaces.xlink, "href"
+                );
+            },
+            "Format": function(node, graphic) {
+                graphic.graphicFormat = this.getChildValue(node);
+            }
+        }
+    },
+    
+    /**
+     * Property: cssMap
+     * {Object} Object mapping supported css property names to OpenLayers
+     *     symbolizer property names.
+     */
+    cssMap: {
+        "stroke": "strokeColor",
+        "stroke-opacity": "strokeOpacity",
+        "stroke-width": "strokeWidth",
+        "stroke-linecap": "strokeLinecap",
+        "stroke-dasharray": "strokeDashstyle",
+        "fill": "fillColor",
+        "fill-opacity": "fillOpacity",
+        "font-family": "fontFamily",
+        "font-size": "fontSize"
+    },
+    
+    /**
+     * Method: getCssProperty
+     * Given a symbolizer property, get the corresponding CSS property
+     *     from the <cssMap>.
+     *
+     * Parameters:
+     * sym - {String} A symbolizer property name.
+     *
+     * Returns:
+     * {String} A CSS property name or null if none found.
+     */
+    getCssProperty: function(sym) {
+        var css = null;
+        for(var prop in this.cssMap) {
+            if(this.cssMap[prop] == sym) {
+                css = prop;
+                break;
+            }
+        }
+        return css;
+    },
+    
+    /**
+     * Method: getGraphicFormat
+     * Given a href for an external graphic, try to determine the mime-type.
+     *     This method doesn't try too hard, and will fall back to
+     *     <defautlGraphicFormat> if one of the known <graphicFormats> is not
+     *     the file extension of the provided href.
+     *
+     * Parameters:
+     * href - {String}
+     *
+     * Returns:
+     * {String} The graphic format.
+     */
+    getGraphicFormat: function(href) {
+        var format, regex;
+        for(var key in this.graphicFormats) {
+            if(this.graphicFormats[key].test(href)) {
+                format = key;
+                break;
+            }
+        }
+        return format || this.defautlGraphicFormat;
+    },
+    
+    /**
+     * Property: defaultGraphicFormat
+     * {String} If none other can be determined from <getGraphicFormat>, this
+     *     default will be returned.
+     */
+    defaultGraphicFormat: "image/png",
+    
+    /**
+     * Property: graphicFormats
+     * {Object} Mapping of image mime-types to regular extensions matching 
+     *     well-known file extensions.
+     */
+    graphicFormats: {
+        "image/jpeg": /\.jpe?g$/i,
+        "image/gif": /\.gif$/i,
+        "image/png": /\.png$/i
+    },
+
+    /**
+     * Method: write
+     *
+     * Parameters:
+     * sld - {Object} An object representing the SLD.
+     *
+     * Returns:
+     * {DOMElement} The root of an SLD document.
+     */
+    write: function(sld) {
+        return this.writers.sld.StyledLayerDescriptor.apply(this, [sld]);
+    },
+    
+    /**
+     * Property: writers
+     * As a compliment to the readers property, this structure contains public
+     *     writing functions grouped by namespace alias and named like the
+     *     node names they produce.
+     */
+    writers: {
+        "sld": {
+            "StyledLayerDescriptor": function(sld) {
+                var root = this.createElementNSPlus(
+                    "StyledLayerDescriptor",
+                    {attributes: {
+                        "version": this.VERSION,
+                        "xsi:schemaLocation": this.schemaLocation
+                    }}
+                );
+                // add in optional name
+                if(sld.name) {
+                    this.writeNode(root, "Name", sld.name);
+                }
+                // add in optional title
+                if(sld.title) {
+                    this.writeNode(root, "Title", sld.title);
+                }
+                // add in optional description
+                if(sld.description) {
+                    this.writeNode(root, "Abstract", sld.description);
+                }
+                // add in named layers
+                for(var name in sld.namedLayers) {
+                    this.writeNode(root, "NamedLayer", sld.namedLayers[name]);
+                }
+                return root;
+            },
+            "Name": function(name) {
+                return this.createElementNSPlus("Name", {value: name});
+            },
+            "Title": function(title) {
+                return this.createElementNSPlus("Title", {value: title});
+            },
+            "Abstract": function(description) {
+                return this.createElementNSPlus(
+                    "Abstract", {value: description}
+                );
+            },
+            "NamedLayer": function(layer) {
+                var node = this.createElementNSPlus("NamedLayer");
+
+                // add in required name
+                this.writeNode(node, "Name", layer.name);
+
+                // optional sld:LayerFeatureConstraints here
+
+                // add in named styles
+                if(layer.namedStyles) {
+                    for(var i=0, len=layer.namedStyles.length; i<len; ++i) {
+                        this.writeNode(
+                            node, "NamedStyle", layer.namedStyles[i]
+                        );
+                    }
+                }
+                
+                // add in user styles
+                if(layer.userStyles) {
+                    for(var i=0, len=layer.userStyles.length; i<len; ++i) {
+                        this.writeNode(
+                            node, "UserStyle", layer.userStyles[i]
+                        );
+                    }
+                }
+                
+                return node;
+            },
+            "NamedStyle": function(name) {
+                var node = this.createElementNSPlus("NamedStyle");
+                this.writeNode(node, "Name", name);
+                return node;
+            },
+            "UserStyle": function(style) {
+                var node = this.createElementNSPlus("UserStyle");
+
+                // add in optional name
+                if(style.name) {
+                    this.writeNode(node, "Name", style.name);
+                }
+                // add in optional title
+                if(style.title) {
+                    this.writeNode(node, "Title", style.title);
+                }
+                // add in optional description
+                if(style.description) {
+                    this.writeNode(node, "Abstract", style.description);
+                }
+                
+                // add isdefault
+                if(style.isDefault) {
+                    this.writeNode(node, "IsDefault", style.isDefault);
+                }
+                
+                // add FeatureTypeStyles
+                this.writeNode(node, "FeatureTypeStyle", style);
+                
+                return node;
+            },
+            "IsDefault": function(bool) {
+                return this.createElementNSPlus(
+                    "IsDefault", {value: (bool) ? "1" : "0"}
+                );
+            },
+            "FeatureTypeStyle": function(style) {
+                var node = this.createElementNSPlus("FeatureTypeStyle");
+                
+                // OpenLayers currently stores no Name, Title, Abstract,
+                // FeatureTypeName, or SemanticTypeIdentifier information
+                // related to FeatureTypeStyle
+                
+                // add in rules
+                for(var i=0, len=style.rules.length; i<len; ++i) {
+                    this.writeNode(node, "Rule", style.rules[i]);
+                }
+                
+                return node;
+            },
+            "Rule": function(rule) {
+                var node = this.createElementNSPlus("Rule");
+
+                // add in optional name
+                if(rule.name) {
+                    this.writeNode(node, "Name", rule.name);
+                }
+                // add in optional title
+                if(rule.title) {
+                    this.writeNode(node, "Title", rule.title);
+                }
+                // add in optional description
+                if(rule.description) {
+                    this.writeNode(node, "Abstract", rule.description);
+                }
+                
+                // add in LegendGraphic here
+                
+                // add in optional filters
+                if(rule.elseFilter) {
+                    this.writeNode(node, "ElseFilter");
+                } else if(rule.filter) {
+                    this.writeNode(node, "ogc:Filter", rule.filter);
+                }
+                
+                // add in scale limits
+                if(rule.minScaleDenominator != undefined) {
+                    this.writeNode(
+                        node, "MinScaleDenominator", rule.minScaleDenominator
+                    );
+                }
+                if(rule.maxScaleDenominator != undefined) {
+                    this.writeNode(
+                        node, "MaxScaleDenominator", rule.maxScaleDenominator
+                    );
+                }
+                
+                // add in symbolizers (relies on geometry type keys)
+                var types = OpenLayers.Style.SYMBOLIZER_PREFIXES;
+                var type, symbolizer;
+                for(var i=0, len=types.length; i<len; ++i) {
+                    type = types[i];
+                    symbolizer = rule.symbolizer[type];
+                    if(symbolizer) {
+                        this.writeNode(
+                            node, type + "Symbolizer", symbolizer
+                        );
+                    }
+                }
+                return node;
+
+            },
+            "ElseFilter": function() {
+                return this.createElementNSPlus("ElseFilter");
+            },
+            "MinScaleDenominator": function(scale) {
+                return this.createElementNSPlus(
+                    "MinScaleDenominator", {value: scale}
+                );
+            },
+            "MaxScaleDenominator": function(scale) {
+                return this.createElementNSPlus(
+                    "MaxScaleDenominator", {value: scale}
+                );
+            },
+            "LineSymbolizer": function(symbolizer) {
+                var node = this.createElementNSPlus("LineSymbolizer");
+                this.writeNode(node, "Stroke", symbolizer);
+                return node;
+            },
+            "Stroke": function(symbolizer) {
+                var node = this.createElementNSPlus("Stroke");
+
+                // GraphicFill here
+                // GraphicStroke here
+
+                // add in CssParameters
+                if(symbolizer.strokeColor != undefined) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "strokeColor"}
+                    );
+                }
+                if(symbolizer.strokeOpacity != undefined) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "strokeOpacity"}
+                    );
+                }
+                if(symbolizer.strokeWidth != undefined) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "strokeWidth"}
+                    );
+                }
+                return node;
+            },
+            "CssParameter": function(obj) {
+                // not handling ogc:expressions for now
+                return this.createElementNSPlus("CssParameter", {
+                    attributes: {name: this.getCssProperty(obj.key)},
+                    value: obj.symbolizer[obj.key]
+                });
+            },
+            "TextSymbolizer": function(symbolizer) {
+                var node = this.createElementNSPlus("TextSymbolizer");
+                // add in optional Label
+                if(symbolizer.label != null) {
+                    this.writeNode(node, "Label", symbolizer.label);
+                }
+                // add in optional Font
+                if(symbolizer.fontFamily != null ||
+                   symbolizer.fontSize != null) {
+                    this.writeNode(node, "Font", symbolizer);
+                }
+                // add in optional Fill
+                if(symbolizer.fillColor != null ||
+                   symbolizer.fillOpacity != null) {
+                    this.writeNode(node, "Fill", symbolizer);
+                }
+                return node;
+            },
+            "Font": function(symbolizer) {
+                var node = this.createElementNSPlus("Font");
+                // add in CssParameters
+                if(symbolizer.fontFamily) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "fontFamily"}
+                    );
+                }
+                if(symbolizer.fontSize) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "fontSize"}
+                    );
+                }
+                return node;
+            },
+            "Label": function(label) {
+                // only the simplest of ogc:expression handled
+                // {label: "some text and a ${propertyName}"}
+                var node = this.createElementNSPlus("Label");
+                var tokens = label.split("${");
+                node.appendChild(this.createTextNode(tokens[0]));
+                var item, last;
+                for(var i=1, len=tokens.length; i<len; i++) {
+                    item = tokens[i];
+                    last = item.indexOf("}"); 
+                    if(last > 0) {
+                        this.writeNode(
+                            node, "ogc:PropertyName",
+                            {property: item.substring(0, last)}
+                        );
+                        node.appendChild(
+                            this.createTextNode(item.substring(++last))
+                        );
+                    } else {
+                        // no ending }, so this is a literal ${
+                        node.appendChild(
+                            this.createTextNode("${" + item)
+                        );
+                    }
+                }
+                return node;
+            },
+            "PolygonSymbolizer": function(symbolizer) {
+                var node = this.createElementNSPlus("PolygonSymbolizer");
+                this.writeNode(node, "Fill", symbolizer);
+                this.writeNode(node, "Stroke", symbolizer);
+                return node;
+            },
+            "Fill": function(symbolizer) {
+                var node = this.createElementNSPlus("Fill");
+                
+                // GraphicFill here
+                
+                // add in CssParameters
+                if(symbolizer.fillColor) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "fillColor"}
+                    );
+                }
+                if(symbolizer.fillOpacity) {
+                    this.writeNode(
+                        node, "CssParameter",
+                        {symbolizer: symbolizer, key: "fillOpacity"}
+                    );
+                }
+                return node;
+            },
+            "PointSymbolizer": function(symbolizer) {
+                var node = this.createElementNSPlus("PointSymbolizer");
+                this.writeNode(node, "Graphic", symbolizer);
+                return node;
+            },
+            "Graphic": function(symbolizer) {
+                var node = this.createElementNSPlus("Graphic");
+                if(symbolizer.externalGraphic != undefined) {
+                    this.writeNode(node, "ExternalGraphic", symbolizer);
+                } else if(symbolizer.graphicName) {
+                    this.writeNode(node, "Mark", symbolizer);
+                }
+                
+                if(symbolizer.graphicOpacity != undefined) {
+                    this.writeNode(node, "Opacity", symbolizer.graphicOpacity);
+                }
+                if(symbolizer.pointRadius != undefined) {
+                    this.writeNode(node, "Size", symbolizer.pointRadius * 2);
+                }
+                if(symbolizer.rotation != undefined) {
+                    this.writeNode(node, "Rotation", symbolizer.rotation);
+                }
+                return node;
+            },
+            "ExternalGraphic": function(symbolizer) {
+                var node = this.createElementNSPlus("ExternalGraphic");
+                this.writeNode(
+                    node, "OnlineResource", symbolizer.externalGraphic
+                );
+                var format = symbolizer.graphicFormat ||
+                             this.getGraphicFormat(symbolizer.externalGraphic);
+                this.writeNode(node, "Format", format);
+                return node;
+            },
+            "Mark": function(symbolizer) {
+                var node = this.createElementNSPlus("Mark");
+                this.writeNode(node, "WellKnownName", symbolizer.graphicName);
+                this.writeNode(node, "Fill", symbolizer);
+                this.writeNode(node, "Stroke", symbolizer);
+                return node;
+            },
+            "WellKnownName": function(name) {
+                return this.createElementNSPlus("WellKnownName", {
+                    value: name
+                });
+            },
+            "Opacity": function(value) {
+                return this.createElementNSPlus("Opacity", {
+                    value: value
+                });
+            },
+            "Size": function(value) {
+                return this.createElementNSPlus("Size", {
+                    value: value
+                });
+            },
+            "Rotation": function(value) {
+                return this.createElementNSPlus("Rotation", {
+                    value: value
+                });
+            },
+            "OnlineResource": function(href) {
+                return this.createElementNSPlus("OnlineResource", {
+                    attributes: {
+                        "xlink:type": "simple",
+                        "xlink:href": href
+                    }
+                });
+            },
+            "Format": function(format) {
+                return this.createElementNSPlus("Format", {
+                    value: format
+                });
+            }
+        }
+    },
+    
+    /**
+     * Methods below this point are of general use for versioned XML parsers.
+     * These are candidates for an abstract class.
+     */
+    
+    /**
+     * Method: getNamespacePrefix
+     * Get the namespace prefix for a given uri from the <namespaces> object.
+     *
+     * Returns:
+     * {String} A namespace prefix or null if none found.
+     */
+    getNamespacePrefix: function(uri) {
+        var prefix = null;
+        if(uri == null) {
+            prefix = this.namespaces[this.defaultPrefix];
+        } else {
+            var gotPrefix = false;
+            for(prefix in this.namespaces) {
+                if(this.namespaces[prefix] == uri) {
+                    gotPrefix = true;
+                    break;
+                }
+            }
+            if(!gotPrefix) {
+                prefix = null;
+            }
+        }
+        return prefix;
+    },
+
+
+    /**
+     * Method: readChildNodes
+     */
+    readChildNodes: function(node, obj) {
+        var children = node.childNodes;
+        var child, group, reader, prefix, local;
+        for(var i=0, len=children.length; i<len; ++i) {
+            child = children[i];
+            if(child.nodeType == 1) {
+                prefix = this.getNamespacePrefix(child.namespaceURI);
+                local = child.nodeName.split(":").pop();
+                group = this.readers[prefix];
+                if(group) {
+                    reader = group[local];
+                    if(reader) {
+                        reader.apply(this, [child, obj]);
+                    }
+                }
+            }
+        }
+    },
+
+    /**
+     * Method: writeNode
+     * Shorthand for applying one of the named writers and appending the
+     *     results to a node.  If a qualified name is not provided for the
+     *     second argument (and a local name is used instead), the namespace
+     *     of the parent node will be assumed.
+     *
+     * Parameters:
+     * parent - {DOMElement} Result will be appended to this node.
+     * name - {String} The name of a node to generate.  If a qualified name
+     *     (e.g. "pre:Name") is used, the namespace prefix is assumed to be
+     *     in the <writers> group.  If a local name is used (e.g. "Name") then
+     *     the namespace of the parent is assumed.
+     * obj - {Object} Structure containing data for the writer.
+     *
+     * Returns:
+     * {DOMElement} The child node.
+     */
+    writeNode: function(parent, name, obj) {
+        var prefix, local;
+        var split = name.indexOf(":");
+        if(split > 0) {
+            prefix = name.substring(0, split);
+            local = name.substring(split + 1);
+        } else {
+            prefix = this.getNamespacePrefix(parent.namespaceURI);
+            local = name;
+        }
+        var child = this.writers[prefix][local].apply(this, [obj]);
+        parent.appendChild(child);
+        return child;
+    },
+    
+    /**
+     * Method: createElementNSPlus
+     * Shorthand for creating namespaced elements with optional attributes and
+     *     child text nodes.
+     *
+     * Parameters:
+     * name - {String} The qualified node name.
+     * options - {Object} Optional object for node configuration.
+     *
+     * Returns:
+     * {Element} An element node.
+     */
+    createElementNSPlus: function(name, options) {
+        options = options || {};
+        var loc = name.indexOf(":");
+        // order of prefix preference
+        // 1. in the uri option
+        // 2. in the prefix option
+        // 3. in the qualified name
+        // 4. from the defaultPrefix
+        var uri = options.uri || this.namespaces[options.prefix];
+        if(!uri) {
+            loc = name.indexOf(":");
+            uri = this.namespaces[name.substring(0, loc)];
+        }
+        if(!uri) {
+            uri = this.namespaces[this.defaultPrefix];
+        }
+        var node = this.createElementNS(uri, name);
+        if(options.attributes) {
+            this.setAttributes(node, options.attributes);
+        }
+        if(options.value) {
+            node.appendChild(this.createTextNode(options.value));
+        }
+        return node;
+    },
+    
+    /**
+     * Method: setAttributes
+     * Set multiple attributes given key value pairs from an object.
+     *
+     * Parameters:
+     * node - {Element} An element node.
+     * obj - {Object || Array} An object whose properties represent attribute
+     *     names and values represent attribute values.  If an attribute name
+     *     is a qualified name ("prefix:local"), the prefix will be looked up
+     *     in the parsers {namespaces} object.  If the prefix is found,
+     *     setAttributeNS will be used instead of setAttribute.
+     */
+    setAttributes: function(node, obj) {
+        var value, loc, alias, uri;
+        for(var name in obj) {
+            value = obj[name].toString();
+            // check for qualified attribute name ("prefix:local")
+            uri = this.namespaces[name.substring(0, name.indexOf(":"))] || null;
+            this.setAttributeNS(node, uri, name, value);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Format.SLD.v1" 
+
+});
+/* ======================================================================
+    OpenLayers/Geometry/Curve.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.Curve
+ * A Curve is a MultiPoint, whose points are assumed to be connected. To 
+ * this end, we provide a "getLength()" function, which iterates through 
+ * the points, summing the distances between them. 
+ * 
+ * Inherits: 
+ *  - <OpenLayers.Geometry.MultiPoint>
+ */
+OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of 
+     *                 components that the collection can include.  A null 
+     *                 value means the component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.Point"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.Curve
+     * 
+     * Parameters:
+     * point - {Array(<OpenLayers.Geometry.Point>)}
+     */
+    initialize: function(points) {
+        OpenLayers.Geometry.MultiPoint.prototype.initialize.apply(this, 
+                                                                  arguments);
+    },
+    
+    /**
+     * APIMethod: getLength
+     * 
+     * Returns:
+     * {Float} The length of the curve
+     */
+    getLength: function() {
+        var length = 0.0;
+        if ( this.components && (this.components.length > 1)) {
+            for(var i=1, len=this.components.length; i<len; i++) {
+                length += this.components[i-1].distanceTo(this.components[i]);
+            }
+        }
+        return length;
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.Curve"
+});
+/* ======================================================================
+    OpenLayers/Format/Filter/v1_0_0.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/Filter/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.Filter.v1_0_0
+ * Write ogc:Filter version 1.0.0.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.Filter.v1>
+ */
+OpenLayers.Format.Filter.v1_0_0 = OpenLayers.Class(
+    OpenLayers.Format.Filter.v1, {
+    
+    /**
+     * Constant: VERSION
+     * {String} 1.0.0
+     */
+    VERSION: "1.0.0",
+    
+    /**
+     * Property: schemaLocation
+     * {String} http://www.opengis.net/ogc/filter/1.0.0/filter.xsd
+     */
+    schemaLocation: "http://www.opengis.net/ogc/filter/1.0.0/filter.xsd",
+
+    /**
+     * Constructor: OpenLayers.Format.Filter.v1_0_0
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.Filter> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.Filter.v1.prototype.initialize.apply(
+            this, [options]
+        );
+    },
+
+    CLASS_NAME: "OpenLayers.Format.Filter.v1_0_0" 
+
+});
+/* ======================================================================
+    OpenLayers/Format/SLD/v1_0_0.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/SLD/v1.js
+ */
+
+/**
+ * Class: OpenLayers.Format.SLD.v1_0_0
+ * Write SLD version 1.0.0.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.SLD.v1>
+ */
+OpenLayers.Format.SLD.v1_0_0 = OpenLayers.Class(
+    OpenLayers.Format.SLD.v1, {
+    
+    /**
+     * Constant: VERSION
+     * {String} 1.0.0
+     */
+    VERSION: "1.0.0",
+    
+    /**
+     * Property: schemaLocation
+     * {String} http://www.opengis.net/sld
+     *   http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd
+     */
+    schemaLocation: "http://www.opengis.net/sld http://schemas.opengis.net/sld/1.0.0/StyledLayerDescriptor.xsd",
+
+    /**
+     * Constructor: OpenLayers.Format.SLD.v1_0_0
+     * Instances of this class are not created directly.  Use the
+     *     <OpenLayers.Format.SLD> constructor instead.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.SLD.v1.prototype.initialize.apply(
+            this, [options]
+        );
+    },
+
+    CLASS_NAME: "OpenLayers.Format.SLD.v1_0_0" 
+
+});
+/* ======================================================================
+    OpenLayers/Geometry/LineString.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/Curve.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LineString
+ * A LineString is a Curve which, once two points have been added to it, can 
+ * never be less than two points long.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Geometry.Curve>
+ */
+OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {
+
+    /**
+     * Constructor: OpenLayers.Geometry.LineString
+     * Create a new LineString geometry
+     *
+     * Parameters:
+     * points - {Array(<OpenLayers.Geometry.Point>)} An array of points used to
+     *          generate the linestring
+     *
+     */
+    initialize: function(points) {
+        OpenLayers.Geometry.Curve.prototype.initialize.apply(this, arguments);        
+    },
+
+    /**
+     * APIMethod: removeComponent
+     * Only allows removal of a point if there are three or more points in 
+     * the linestring. (otherwise the result would be just a single point)
+     *
+     * Parameters: 
+     * point - {<OpenLayers.Geometry.Point>} The point to be removed
+     */
+    removeComponent: function(point) {
+        if ( this.components && (this.components.length > 2)) {
+            OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, 
+                                                                  arguments);
+        }
+    },
+    
+    /**
+     * APIMethod: intersects
+     * Test for instersection between two geometries.  This is a cheapo
+     *     implementation of the Bently-Ottmann algorigithm.  It doesn't
+     *     really keep track of a sweep line data structure.  It is closer
+     *     to the brute force method, except that segments are sorted and
+     *     potential intersections are only calculated when bounding boxes
+     *     intersect.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     *
+     * Returns:
+     * {Boolean} The input geometry intersects this geometry.
+     */
+    intersects: function(geometry) {
+        var intersect = false;
+        var type = geometry.CLASS_NAME;
+        if(type == "OpenLayers.Geometry.LineString" ||
+           type == "OpenLayers.Geometry.LinearRing" ||
+           type == "OpenLayers.Geometry.Point") {
+            var segs1 = this.getSortedSegments();
+            var segs2;
+            if(type == "OpenLayers.Geometry.Point") {
+                segs2 = [{
+                    x1: geometry.x, y1: geometry.y,
+                    x2: geometry.x, y2: geometry.y
+                }];
+            } else {
+                segs2 = geometry.getSortedSegments();
+            }
+            var seg1, seg1x1, seg1x2, seg1y1, seg1y2,
+                seg2, seg2y1, seg2y2;
+            // sweep right
+            outer: for(var i=0, len=segs1.length; i<len; ++i) {
+                seg1 = segs1[i];
+                seg1x1 = seg1.x1;
+                seg1x2 = seg1.x2;
+                seg1y1 = seg1.y1;
+                seg1y2 = seg1.y2;
+                inner: for(var j=0, jlen=segs2.length; j<jlen; ++j) {
+                    seg2 = segs2[j];
+                    if(seg2.x1 > seg1x2) {
+                        // seg1 still left of seg2
+                        break;
+                    }
+                    if(seg2.x2 < seg1x1) {
+                        // seg2 still left of seg1
+                        continue;
+                    }
+                    seg2y1 = seg2.y1;
+                    seg2y2 = seg2.y2;
+                    if(Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) {
+                        // seg2 above seg1
+                        continue;
+                    }
+                    if(Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) {
+                        // seg2 below seg1
+                        continue;
+                    }
+                    if(OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) {
+                        intersect = true;
+                        break outer;
+                    }
+                }
+            }
+        } else {
+            intersect = geometry.intersects(this);
+        }
+        return intersect;
+    },
+    
+    /**
+     * Method: getSortedSegments
+     *
+     * Returns:
+     * {Array} An array of segment objects.  Segment objects have properties
+     *     x1, y1, x2, and y2.  The start point is represented by x1 and y1.
+     *     The end point is represented by x2 and y2.  Start and end are
+     *     ordered so that x1 < x2.
+     */
+    getSortedSegments: function() {
+        var numSeg = this.components.length - 1;
+        var segments = new Array(numSeg);
+        for(var i=0; i<numSeg; ++i) {
+            point1 = this.components[i];
+            point2 = this.components[i + 1];
+            if(point1.x < point2.x) {
+                segments[i] = {
+                    x1: point1.x,
+                    y1: point1.y,
+                    x2: point2.x,
+                    y2: point2.y
+                };
+            } else {
+                segments[i] = {
+                    x1: point2.x,
+                    y1: point2.y,
+                    x2: point1.x,
+                    y2: point1.y
+                };
+            }
+        }
+        // more efficient to define this somewhere static
+        function byX1(seg1, seg2) {
+            return seg1.x1 - seg2.x1;
+        }
+        return segments.sort(byX1);
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.LineString"
+});
+/* ======================================================================
+    OpenLayers/Format/GML.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GML
+ * Read/Wite GML. Create a new instance with the <OpenLayers.Format.GML>
+ *     constructor.  Supports the GML simple features profile.
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format>
+ */
+OpenLayers.Format.GML = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /*
+     * APIProperty: featureNS
+     * {String} Namespace used for feature attributes.  Default is
+     *     "http://mapserver.gis.umn.edu/mapserver".
+     */
+    featureNS: "http://mapserver.gis.umn.edu/mapserver",
+    
+    /**
+     * APIProperty: featurePrefix
+     * {String} Namespace alias (or prefix) for feature nodes.  Default is
+     *     "feature".
+     */
+    featurePrefix: "feature",
+    
+    /*
+     * APIProperty: featureName
+     * {String} Element name for features. Default is "featureMember".
+     */
+    featureName: "featureMember", 
+    
+    /*
+     * APIProperty: layerName
+     * {String} Name of data layer. Default is "features".
+     */
+    layerName: "features",
+    
+    /**
+     * APIProperty: geometryName
+     * {String} Name of geometry element.  Defaults to "geometry".
+     */
+    geometryName: "geometry",
+    
+    /** 
+     * APIProperty: collectionName
+     * {String} Name of featureCollection element.
+     */
+    collectionName: "FeatureCollection",
+    
+    /**
+     * APIProperty: gmlns
+     * {String} GML Namespace.
+     */
+    gmlns: "http://www.opengis.net/gml",
+
+    /**
+     * APIProperty: extractAttributes
+     * {Boolean} Extract attributes from GML.
+     */
+    extractAttributes: true,
+    
+    /**
+     * APIProperty: xy
+     * {Boolean} Order of the GML coordinate true:(x,y) or false:(y,x)
+     * Changing is not recommended, a new Format should be instantiated.
+     */ 
+    xy: true,
+    
+    /**
+     * Constructor: OpenLayers.Format.GML
+     * Create a new parser for GML.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        // compile regular expressions once instead of every time they are used
+        this.regExes = {
+            trimSpace: (/^\s*|\s*$/g),
+            removeSpace: (/\s*/g),
+            splitSpace: (/\s+/),
+            trimComma: (/\s*,\s*/g)
+        };
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: read
+     * Read data from a string, and return a list of features. 
+     * 
+     * Parameters:
+     * data - {String} or {DOMElement} data to read/parse.
+     *
+     * Returns:
+     * {Array(<OpenLayers.Feature.Vector>)} An array of features.
+     */
+    read: function(data) {
+        if(typeof data == "string") { 
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+        var featureNodes = this.getElementsByTagNameNS(data.documentElement,
+                                                       this.gmlns,
+                                                       this.featureName);
+        var features = [];
+        for(var i=0; i<featureNodes.length; i++) {
+            var feature = this.parseFeature(featureNodes[i]);
+            if(feature) {
+                features.push(feature);
+            }
+        }
+        return features;
+    },
+    
+    /**
+     * Method: parseFeature
+     * This function is the core of the GML parsing code in OpenLayers.
+     *    It creates the geometries that are then attached to the returned
+     *    feature, and calls parseAttributes() to get attribute data out.
+     *    
+     * Parameters:
+     * node - {DOMElement} A GML feature node. 
+     */
+    parseFeature: function(node) {
+        // only accept on geometry per feature - look for highest "order"
+        var order = ["MultiPolygon", "Polygon",
+                     "MultiLineString", "LineString",
+                     "MultiPoint", "Point", "Envelope"];
+        var type, nodeList, geometry, parser;
+        for(var i=0; i<order.length; ++i) {
+            type = order[i];
+            nodeList = this.getElementsByTagNameNS(node, this.gmlns, type);
+            if(nodeList.length > 0) {
+                // only deal with first geometry of this type
+                var parser = this.parseGeometry[type.toLowerCase()];
+                if(parser) {
+                    geometry = parser.apply(this, [nodeList[0]]);
+                    if (this.internalProjection && this.externalProjection) {
+                        geometry.transform(this.externalProjection, 
+                                           this.internalProjection); 
+                    }                       
+                } else {
+                    OpenLayers.Console.error(OpenLayers.i18n(
+                                "unsupportedGeometryType", {'geomType':type}));
+                }
+                // stop looking for different geometry types
+                break;
+            }
+        }
+        
+        // construct feature (optionally with attributes)
+        var attributes;
+        if(this.extractAttributes) {
+            attributes = this.parseAttributes(node);
+        }
+        var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+        
+        feature.gml = {
+            featureType: node.firstChild.nodeName.split(":")[1],
+            featureNS: node.firstChild.namespaceURI,
+            featureNSPrefix: node.firstChild.prefix
+        };
+                
+        // assign fid - this can come from a "fid" or "id" attribute
+        var childNode = node.firstChild;
+        var fid;
+        while(childNode) {
+            if(childNode.nodeType == 1) {
+                fid = childNode.getAttribute("fid") ||
+                      childNode.getAttribute("id");
+                if(fid) {
+                    break;
+                }
+            }
+            childNode = childNode.nextSibling;
+        }
+        feature.fid = fid;
+        return feature;
+    },
+    
+    /**
+     * Property: parseGeometry
+     * Properties of this object are the functions that parse geometries based
+     *     on their type.
+     */
+    parseGeometry: {
+        
+        /**
+         * Method: parseGeometry.point
+         * Given a GML node representing a point geometry, create an OpenLayers
+         *     point geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Point>} A point geometry.
+         */
+        point: function(node) {
+            /**
+             * Three coordinate variations to consider:
+             * 1) <gml:pos>x y z</gml:pos>
+             * 2) <gml:coordinates>x, y, z</gml:coordinates>
+             * 3) <gml:coord><gml:X>x</gml:X><gml:Y>y</gml:Y></gml:coord>
+             */
+            var nodeList, coordString;
+            var coords = [];
+
+            // look for <gml:pos>
+            var nodeList = this.getElementsByTagNameNS(node, this.gmlns, "pos");
+            if(nodeList.length > 0) {
+                coordString = nodeList[0].firstChild.nodeValue;
+                coordString = coordString.replace(this.regExes.trimSpace, "");
+                coords = coordString.split(this.regExes.splitSpace);
+            }
+
+            // look for <gml:coordinates>
+            if(coords.length == 0) {
+                nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "coordinates");
+                if(nodeList.length > 0) {
+                    coordString = nodeList[0].firstChild.nodeValue;
+                    coordString = coordString.replace(this.regExes.removeSpace,
+                                                      "");
+                    coords = coordString.split(",");
+                }
+            }
+
+            // look for <gml:coord>
+            if(coords.length == 0) {
+                nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "coord");
+                if(nodeList.length > 0) {
+                    var xList = this.getElementsByTagNameNS(nodeList[0],
+                                                            this.gmlns, "X");
+                    var yList = this.getElementsByTagNameNS(nodeList[0],
+                                                            this.gmlns, "Y");
+                    if(xList.length > 0 && yList.length > 0) {
+                        coords = [xList[0].firstChild.nodeValue,
+                                  yList[0].firstChild.nodeValue];
+                    }
+                }
+            }
+                
+            // preserve third dimension
+            if(coords.length == 2) {
+                coords[2] = null;
+            }
+            
+            if (this.xy) {
+                return new OpenLayers.Geometry.Point(coords[0], coords[1],
+                                                 coords[2]);
+            }
+            else{
+                return new OpenLayers.Geometry.Point(coords[1], coords[0],
+                                                 coords[2]);
+            }
+        },
+        
+        /**
+         * Method: parseGeometry.multipoint
+         * Given a GML node representing a multipoint geometry, create an
+         *     OpenLayers multipoint geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
+         */
+        multipoint: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "Point");
+            var components = [];
+            if(nodeList.length > 0) {
+                var point;
+                for(var i=0; i<nodeList.length; ++i) {
+                    point = this.parseGeometry.point.apply(this, [nodeList[i]]);
+                    if(point) {
+                        components.push(point);
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.MultiPoint(components);
+        },
+        
+        /**
+         * Method: parseGeometry.linestring
+         * Given a GML node representing a linestring geometry, create an
+         *     OpenLayers linestring geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+         */
+        linestring: function(node, ring) {
+            /**
+             * Two coordinate variations to consider:
+             * 1) <gml:posList dimension="d">x0 y0 z0 x1 y1 z1</gml:posList>
+             * 2) <gml:coordinates>x0, y0, z0 x1, y1, z1</gml:coordinates>
+             */
+            var nodeList, coordString;
+            var coords = [];
+            var points = [];
+
+            // look for <gml:posList>
+            nodeList = this.getElementsByTagNameNS(node, this.gmlns, "posList");
+            if(nodeList.length > 0) {
+                coordString = this.concatChildValues(nodeList[0]);
+                coordString = coordString.replace(this.regExes.trimSpace, "");
+                coords = coordString.split(this.regExes.splitSpace);
+                var dim = parseInt(nodeList[0].getAttribute("dimension"));
+                var j, x, y, z;
+                for(var i=0; i<coords.length/dim; ++i) {
+                    j = i * dim;
+                    x = coords[j];
+                    y = coords[j+1];
+                    z = (dim == 2) ? null : coords[j+2];
+                    if (this.xy) {
+                        points.push(new OpenLayers.Geometry.Point(x, y, z));
+                    } else {
+                        points.push(new OpenLayers.Geometry.Point(y, x, z));
+                    }
+                }
+            }
+
+            // look for <gml:coordinates>
+            if(coords.length == 0) {
+                nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "coordinates");
+                if(nodeList.length > 0) {
+                    coordString = this.concatChildValues(nodeList[0]);
+                    coordString = coordString.replace(this.regExes.trimSpace,
+                                                      "");
+                    coordString = coordString.replace(this.regExes.trimComma,
+                                                      ",");
+                    var pointList = coordString.split(this.regExes.splitSpace);
+                    for(var i=0; i<pointList.length; ++i) {
+                        coords = pointList[i].split(",");
+                        if(coords.length == 2) {
+                            coords[2] = null;
+                        }
+                        if (this.xy) {
+                            points.push(new OpenLayers.Geometry.Point(coords[0],
+                                                                  coords[1],
+                                                                  coords[2]));
+                        } else {
+                            points.push(new OpenLayers.Geometry.Point(coords[1],
+                                                                  coords[0],
+                                                                  coords[2]));
+                        }
+                    }
+                }
+            }
+
+            var line = null;
+            if(points.length != 0) {
+                if(ring) {
+                    line = new OpenLayers.Geometry.LinearRing(points);
+                } else {
+                    line = new OpenLayers.Geometry.LineString(points);
+                }
+            }
+            return line;
+        },
+        
+        /**
+         * Method: parseGeometry.multilinestring
+         * Given a GML node representing a multilinestring geometry, create an
+         *     OpenLayers multilinestring geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.MultiLineString>} A multilinestring geometry.
+         */
+        multilinestring: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "LineString");
+            var components = [];
+            if(nodeList.length > 0) {
+                var line;
+                for(var i=0; i<nodeList.length; ++i) {
+                    line = this.parseGeometry.linestring.apply(this,
+                                                               [nodeList[i]]);
+                    if(line) {
+                        components.push(line);
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.MultiLineString(components);
+        },
+        
+        /**
+         * Method: parseGeometry.polygon
+         * Given a GML node representing a polygon geometry, create an
+         *     OpenLayers polygon geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+         */
+        polygon: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "LinearRing");
+            var components = [];
+            if(nodeList.length > 0) {
+                // this assumes exterior ring first, inner rings after
+                var ring;
+                for(var i=0; i<nodeList.length; ++i) {
+                    ring = this.parseGeometry.linestring.apply(this,
+                                                        [nodeList[i], true]);
+                    if(ring) {
+                        components.push(ring);
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.Polygon(components);
+        },
+        
+        /**
+         * Method: parseGeometry.multipolygon
+         * Given a GML node representing a multipolygon geometry, create an
+         *     OpenLayers multipolygon geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A GML node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.MultiPolygon>} A multipolygon geometry.
+         */
+        multipolygon: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.gmlns,
+                                                       "Polygon");
+            var components = [];
+            if(nodeList.length > 0) {
+                var polygon;
+                for(var i=0; i<nodeList.length; ++i) {
+                    polygon = this.parseGeometry.polygon.apply(this,
+                                                               [nodeList[i]]);
+                    if(polygon) {
+                        components.push(polygon);
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.MultiPolygon(components);
+        },
+        
+        envelope: function(node) {
+            var components = [];
+            var coordString;
+            var envelope;
+            
+            var lpoint = this.getElementsByTagNameNS(node, this.gmlns, "lowerCorner");
+            if (lpoint.length > 0) {
+                var coords = [];
+                
+                if(lpoint.length > 0) {
+                    coordString = lpoint[0].firstChild.nodeValue;
+                    coordString = coordString.replace(this.regExes.trimSpace, "");
+                    coords = coordString.split(this.regExes.splitSpace);
+                }
+                
+                if(coords.length == 2) {
+                    coords[2] = null;
+                }
+                if (this.xy) {
+                    var lowerPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
+                } else {
+                    var lowerPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
+                }
+            }
+            
+            var upoint = this.getElementsByTagNameNS(node, this.gmlns, "upperCorner");
+            if (upoint.length > 0) {
+                var coords = [];
+                
+                if(upoint.length > 0) {
+                    coordString = upoint[0].firstChild.nodeValue;
+                    coordString = coordString.replace(this.regExes.trimSpace, "");
+                    coords = coordString.split(this.regExes.splitSpace);
+                }
+                
+                if(coords.length == 2) {
+                    coords[2] = null;
+                }
+                if (this.xy) {
+                    var upperPoint = new OpenLayers.Geometry.Point(coords[0], coords[1],coords[2]);
+                } else {
+                    var upperPoint = new OpenLayers.Geometry.Point(coords[1], coords[0],coords[2]);
+                }
+            }
+            
+            if (lowerPoint && upperPoint) {
+                components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
+                components.push(new OpenLayers.Geometry.Point(upperPoint.x, lowerPoint.y));
+                components.push(new OpenLayers.Geometry.Point(upperPoint.x, upperPoint.y));
+                components.push(new OpenLayers.Geometry.Point(lowerPoint.x, upperPoint.y));
+                components.push(new OpenLayers.Geometry.Point(lowerPoint.x, lowerPoint.y));
+                
+                var ring = new OpenLayers.Geometry.LinearRing(components);
+                envelope = new OpenLayers.Geometry.Polygon([ring]);
+            }
+            return envelope; 
+        }
+    },
+    
+    /**
+     * Method: parseAttributes
+     *
+     * Parameters:
+     * node - {<DOMElement>}
+     *
+     * Returns:
+     * {Object} An attributes object.
+     */
+    parseAttributes: function(node) {
+        var attributes = {};
+        // assume attributes are children of the first type 1 child
+        var childNode = node.firstChild;
+        var children, i, child, grandchildren, grandchild, name, value;
+        while(childNode) {
+            if(childNode.nodeType == 1) {
+                // attributes are type 1 children with one type 3 child
+                children = childNode.childNodes;
+                for(i=0; i<children.length; ++i) {
+                    child = children[i];
+                    if(child.nodeType == 1) {
+                        grandchildren = child.childNodes;
+                        if(grandchildren.length == 1) {
+                            grandchild = grandchildren[0];
+                            if(grandchild.nodeType == 3 ||
+                               grandchild.nodeType == 4) {
+                                name = (child.prefix) ?
+                                        child.nodeName.split(":")[1] :
+                                        child.nodeName;
+                                value = grandchild.nodeValue.replace(
+                                                this.regExes.trimSpace, "");
+                                attributes[name] = value;
+                            }
+                        }
+                    }
+                }
+                break;
+            }
+            childNode = childNode.nextSibling;
+        }
+        return attributes;
+    },
+    
+    /**
+     * APIMethod: write
+     * Generate a GML document string given a list of features. 
+     * 
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} List of features to
+     *     serialize into a string.
+     *
+     * Returns:
+     * {String} A string representing the GML document.
+     */
+    write: function(features) {
+        if(!(features instanceof Array)) {
+            features = [features];
+        }
+        var gml = this.createElementNS("http://www.opengis.net/wfs",
+                                       "wfs:" + this.collectionName);
+        for(var i=0; i<features.length; i++) {
+            gml.appendChild(this.createFeatureXML(features[i]));
+        }
+        return OpenLayers.Format.XML.prototype.write.apply(this, [gml]);
+    },
+
+    /** 
+     * Method: createFeatureXML
+     * Accept an OpenLayers.Feature.Vector, and build a GML node for it.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} The feature to be built as GML.
+     *
+     * Returns:
+     * {DOMElement} A node reprensting the feature in GML.
+     */
+    createFeatureXML: function(feature) {
+        var geometry = feature.geometry;
+        var geometryNode = this.buildGeometryNode(geometry);
+        var geomContainer = this.createElementNS(this.featureNS,
+                                                 this.featurePrefix + ":" +
+                                                 this.geometryName);
+        geomContainer.appendChild(geometryNode);
+        var featureNode = this.createElementNS(this.gmlns,
+                                               "gml:" + this.featureName);
+        var featureContainer = this.createElementNS(this.featureNS,
+                                                    this.featurePrefix + ":" +
+                                                    this.layerName);
+        var fid = feature.fid || feature.id;
+        featureContainer.setAttribute("fid", fid);
+        featureContainer.appendChild(geomContainer);
+        for(var attr in feature.attributes) {
+            var attrText = this.createTextNode(feature.attributes[attr]); 
+            var nodename = attr.substring(attr.lastIndexOf(":") + 1);
+            var attrContainer = this.createElementNS(this.featureNS,
+                                                     this.featurePrefix + ":" +
+                                                     nodename);
+            attrContainer.appendChild(attrText);
+            featureContainer.appendChild(attrContainer);
+        }    
+        featureNode.appendChild(featureContainer);
+        return featureNode;
+    },
+    
+    /**
+     * APIMethod: buildGeometryNode
+     */
+    buildGeometryNode: function(geometry) {
+        if (this.externalProjection && this.internalProjection) {
+            geometry = geometry.clone();
+            geometry.transform(this.internalProjection, 
+                               this.externalProjection);
+        }    
+        var className = geometry.CLASS_NAME;
+        var type = className.substring(className.lastIndexOf(".") + 1);
+        var builder = this.buildGeometry[type.toLowerCase()];
+        return builder.apply(this, [geometry]);
+    },
+
+    /**
+     * Property: buildGeometry
+     * Object containing methods to do the actual geometry node building
+     *     based on geometry type.
+     */
+    buildGeometry: {
+        // TBD retrieve the srs from layer
+        // srsName is non-standard, so not including it until it's right.
+        // gml.setAttribute("srsName",
+        //                  "http://www.opengis.net/gml/srs/epsg.xml#4326");
+
+        /**
+         * Method: buildGeometry.point
+         * Given an OpenLayers point geometry, create a GML point.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML point node.
+         */
+        point: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:Point");
+            gml.appendChild(this.buildCoordinatesNode(geometry));
+            return gml;
+        },
+        
+        /**
+         * Method: buildGeometry.multipoint
+         * Given an OpenLayers multipoint geometry, create a GML multipoint.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.MultiPoint>} A multipoint geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML multipoint node.
+         */
+        multipoint: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:MultiPoint");
+            var points = geometry.components;
+            var pointMember, pointGeom;
+            for(var i=0; i<points.length; i++) { 
+                pointMember = this.createElementNS(this.gmlns,
+                                                   "gml:pointMember");
+                pointGeom = this.buildGeometry.point.apply(this,
+                                                               [points[i]]);
+                pointMember.appendChild(pointGeom);
+                gml.appendChild(pointMember);
+            }
+            return gml;            
+        },
+        
+        /**
+         * Method: buildGeometry.linestring
+         * Given an OpenLayers linestring geometry, create a GML linestring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML linestring node.
+         */
+        linestring: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:LineString");
+            gml.appendChild(this.buildCoordinatesNode(geometry));
+            return gml;
+        },
+        
+        /**
+         * Method: buildGeometry.multilinestring
+         * Given an OpenLayers multilinestring geometry, create a GML
+         *     multilinestring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.MultiLineString>} A multilinestring
+         *     geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML multilinestring node.
+         */
+        multilinestring: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:MultiLineString");
+            var lines = geometry.components;
+            var lineMember, lineGeom;
+            for(var i=0; i<lines.length; ++i) {
+                lineMember = this.createElementNS(this.gmlns,
+                                                  "gml:lineStringMember");
+                lineGeom = this.buildGeometry.linestring.apply(this,
+                                                                   [lines[i]]);
+                lineMember.appendChild(lineGeom);
+                gml.appendChild(lineMember);
+            }
+            return gml;
+        },
+        
+        /**
+         * Method: buildGeometry.linearring
+         * Given an OpenLayers linearring geometry, create a GML linearring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML linearring node.
+         */
+        linearring: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:LinearRing");
+            gml.appendChild(this.buildCoordinatesNode(geometry));
+            return gml;
+        },
+        
+        /**
+         * Method: buildGeometry.polygon
+         * Given an OpenLayers polygon geometry, create a GML polygon.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML polygon node.
+         */
+        polygon: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:Polygon");
+            var rings = geometry.components;
+            var ringMember, ringGeom, type;
+            for(var i=0; i<rings.length; ++i) {
+                type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+                ringMember = this.createElementNS(this.gmlns,
+                                                  "gml:" + type);
+                ringGeom = this.buildGeometry.linearring.apply(this,
+                                                                   [rings[i]]);
+                ringMember.appendChild(ringGeom);
+                gml.appendChild(ringMember);
+            }
+            return gml;
+        },
+        
+        /**
+         * Method: buildGeometry.multipolygon
+         * Given an OpenLayers multipolygon geometry, create a GML multipolygon.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.MultiPolygon>} A multipolygon
+         *     geometry.
+         *
+         * Returns:
+         * {DOMElement} A GML multipolygon node.
+         */
+        multipolygon: function(geometry) {
+            var gml = this.createElementNS(this.gmlns, "gml:MultiPolygon");
+            var polys = geometry.components;
+            var polyMember, polyGeom;
+            for(var i=0; i<polys.length; ++i) {
+                polyMember = this.createElementNS(this.gmlns,
+                                                  "gml:polygonMember");
+                polyGeom = this.buildGeometry.polygon.apply(this,
+                                                                [polys[i]]);
+                polyMember.appendChild(polyGeom);
+                gml.appendChild(polyMember);
+            }
+            return gml;
+        }
+    },
+
+    /**
+     * Method: buildCoordinates
+     * builds the coordinates XmlNode
+     * (code)
+     * <gml:coordinates decimal="." cs="," ts=" ">...</gml:coordinates>
+     * (end)
+     * Parameters: 
+     * geometry - {<OpenLayers.Geometry>} 
+     *
+     * Returns:
+     * {XmlNode} created xmlNode
+     */
+    buildCoordinatesNode: function(geometry) {
+        var coordinatesNode = this.createElementNS(this.gmlns,
+                                                   "gml:coordinates");
+        coordinatesNode.setAttribute("decimal", ".");
+        coordinatesNode.setAttribute("cs", ",");
+        coordinatesNode.setAttribute("ts", " ");
+        
+        var points = (geometry.components) ? geometry.components : [geometry];
+        var parts = [];
+        for(var i=0; i<points.length; i++) {
+            parts.push(points[i].x + "," + points[i].y);
+        }
+
+        var txtNode = this.createTextNode(parts.join(" "));
+        coordinatesNode.appendChild(txtNode);
+        
+        return coordinatesNode;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.GML" 
+});
+/* ======================================================================
+    OpenLayers/Format/GPX.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ *
+ * Class: OpenLayers.Format.GPX
+ * Read/write GPX parser. Create a new instance with the 
+ *     <OpenLayers.Format.GPX> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GPX = OpenLayers.Class(OpenLayers.Format.XML, {
+   /**
+    * APIProperty: extractWaypoints
+    * {Boolean} Extract waypoints from GPX. (default: true)
+    */
+    extractWaypoints: true,
+    
+   /**
+    * APIProperty: extractTracks
+    * {Boolean} Extract tracks from GPX. (default: true)
+    */
+    extractTracks: true,
+    
+   /**
+    * APIProperty: extractRoutes
+    * {Boolean} Extract routes from GPX. (default: true)
+    */
+    extractRoutes: true,
+    
+    /**
+     * APIProperty: extractAttributes
+     * {Boolean} Extract feature attributes from GPX. (default: true)
+     *     NOTE: Attributes as part of extensions to the GPX standard may not
+     *     be extracted.
+     */
+    extractAttributes: true,
+    
+    /**
+     * Constructor: OpenLayers.Format.GPX
+     * Create a new parser for GPX.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+    
+    /**
+     * APIMethod: read
+     * Return a list of features from a GPX doc
+     *
+     * Parameters:
+     * doc - {Element} 
+     *
+     * Returns:
+     * An Array of <OpenLayers.Feature.Vector>s
+     */
+    read: function(doc) {
+        if (typeof doc == "string") { 
+            doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+        }
+        var features = [];
+        
+        if(this.extractWaypoints) {
+            var waypoints = doc.getElementsByTagName("wpt");
+            for (var l = 0, len = waypoints.length; l < len; l++) {
+                var attrs = {};
+                if(this.extractAttributes) {
+                    attrs = this.parseAttributes(waypoints[l]);
+                }
+                var wpt = new OpenLayers.Geometry.Point(waypoints[l].getAttribute("lon"), waypoints[l].getAttribute("lat"));
+                features.push(new OpenLayers.Feature.Vector(wpt, attrs));
+            }
+        }
+        
+        if(this.extractTracks) {
+            var tracks = doc.getElementsByTagName("trk");
+            for (var i=0, len=tracks.length; i<len; i++) {
+                // Attributes are only in trk nodes, not trkseg nodes
+                var attrs = {}
+                if(this.extractAttributes) {
+                    attrs = this.parseAttributes(tracks[i]);
+                }
+                
+                var segs = this.getElementsByTagNameNS(tracks[i], tracks[i].namespaceURI, "trkseg");
+                for (var j = 0, seglen = segs.length; j < seglen; j++) {
+                    // We don't yet support extraction of trkpt attributes
+                    // All trksegs of a trk get that trk's attributes
+                    var track = this.extractSegment(segs[j], "trkpt");
+                    features.push(new OpenLayers.Feature.Vector(track, attrs));
+                }
+            }
+        }
+        
+        if(this.extractRoutes) {
+            var routes = doc.getElementsByTagName("rte");
+            for (var k=0, klen=routes.length; k<klen; k++) {
+                var attrs = {}
+                if(this.extractAttributes) {
+                    attrs = this.parseAttributes(routes[k]);
+                }
+                var route = this.extractSegment(routes[k], "rtept");
+                features.push(new OpenLayers.Feature.Vector(route, attrs));
+            }
+        }
+        
+        if (this.internalProjection && this.externalProjection) {
+            for (var g = 0, featLength = features.length; g < featLength; g++) {
+                features[g].geometry.transform(this.externalProjection,
+                                    this.internalProjection);
+            }
+        }
+        
+        return features;
+    },
+    
+   /**
+    * Method: extractSegment
+    *
+    * Parameters:
+    * segment - {<DOMElement>} a trkseg or rte node to parse
+    * segmentType - {String} nodeName of waypoints that form the line
+    *
+    * Returns:
+    * {<OpenLayers.Geometry.LineString>} A linestring geometry
+    */
+    extractSegment: function(segment, segmentType) {
+        var points = this.getElementsByTagNameNS(segment, segment.namespaceURI, segmentType);
+        var point_features = [];
+        for (var i = 0, len = points.length; i < len; i++) {
+            point_features.push(new OpenLayers.Geometry.Point(points[i].getAttribute("lon"), points[i].getAttribute("lat")));
+        }
+        return new OpenLayers.Geometry.LineString(point_features);
+    },
+    
+    /**
+     * Method: parseAttributes
+     *
+     * Parameters:
+     * node - {<DOMElement>}
+     *
+     * Returns:
+     * {Object} An attributes object.
+     */
+    parseAttributes: function(node) {
+        // node is either a wpt, trk or rte
+        // attributes are children of the form <attr>value</attr>
+        var attributes = {};
+        var attrNode = node.firstChild;
+        while(attrNode) {
+            if(attrNode.nodeType == 1) {
+                var value = attrNode.firstChild;
+                if(value.nodeType == 3 || value.nodeType == 4) {
+                    name = (attrNode.prefix) ?
+                        attrNode.nodeName.split(":")[1] :
+                        attrNode.nodeName;
+                    attributes[name] = value.nodeValue;
+                }
+            }
+            attrNode = attrNode.nextSibling;
+        }
+        return attributes;
+    },
+    
+    CLASS_NAME: "OpenLayers.Format.GPX"
+});
+/* ======================================================================
+    OpenLayers/Format/GeoJSON.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/JSON.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/MultiPoint.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/MultiLineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/MultiPolygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GeoJSON
+ * Read and write GeoJSON. Create a new parser with the
+ *     <OpenLayers.Format.GeoJSON> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.JSON>
+ */
+OpenLayers.Format.GeoJSON = OpenLayers.Class(OpenLayers.Format.JSON, {
+
+    /**
+     * Constructor: OpenLayers.Format.GeoJSON
+     * Create a new parser for GeoJSON.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.JSON.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: read
+     * Deserialize a GeoJSON string.
+     *
+     * Parameters:
+     * json - {String} A GeoJSON string
+     * type - {String} Optional string that determines the structure of
+     *     the output.  Supported values are "Geometry", "Feature", and
+     *     "FeatureCollection".  If absent or null, a default of
+     *     "FeatureCollection" is assumed.
+     * filter - {Function} A function which will be called for every key and
+     *     value at every level of the final result. Each value will be
+     *     replaced by the result of the filter function. This can be used to
+     *     reform generic objects into instances of classes, or to transform
+     *     date strings into Date objects.
+     *
+     * Returns: 
+     * {Object} The return depends on the value of the type argument. If type
+     *     is "FeatureCollection" (the default), the return will be an array
+     *     of <OpenLayers.Feature.Vector>. If type is "Geometry", the input json
+     *     must represent a single geometry, and the return will be an
+     *     <OpenLayers.Geometry>.  If type is "Feature", the input json must
+     *     represent a single feature, and the return will be an
+     *     <OpenLayers.Feature.Vector>.
+     */
+    read: function(json, type, filter) {
+        type = (type) ? type : "FeatureCollection";
+        var results = null;
+        var obj = null;
+        if (typeof json == "string") {
+            obj = OpenLayers.Format.JSON.prototype.read.apply(this,
+                                                              [json, filter]);
+        } else { 
+            obj = json;
+        }    
+        if(!obj) {
+            OpenLayers.Console.error("Bad JSON: " + json);
+        } else if(typeof(obj.type) != "string") {
+            OpenLayers.Console.error("Bad GeoJSON - no type: " + json);
+        } else if(this.isValidType(obj, type)) {
+            switch(type) {
+                case "Geometry":
+                    try {
+                        results = this.parseGeometry(obj);
+                    } catch(err) {
+                        OpenLayers.Console.error(err);
+                    }
+                    break;
+                case "Feature":
+                    try {
+                        results = this.parseFeature(obj);
+                        results.type = "Feature";
+                    } catch(err) {
+                        OpenLayers.Console.error(err);
+                    }
+                    break;
+                case "FeatureCollection":
+                    // for type FeatureCollection, we allow input to be any type
+                    results = [];
+                    switch(obj.type) {
+                        case "Feature":
+                            try {
+                                results.push(this.parseFeature(obj));
+                            } catch(err) {
+                                results = null;
+                                OpenLayers.Console.error(err);
+                            }
+                            break;
+                        case "FeatureCollection":
+                            for(var i=0, len=obj.features.length; i<len; ++i) {
+                                try {
+                                    results.push(this.parseFeature(obj.features[i]));
+                                } catch(err) {
+                                    results = null;
+                                    OpenLayers.Console.error(err);
+                                }
+                            }
+                            break;
+                        default:
+                            try {
+                                var geom = this.parseGeometry(obj);
+                                results.push(new OpenLayers.Feature.Vector(geom));
+                            } catch(err) {
+                                results = null;
+                                OpenLayers.Console.error(err);
+                            }
+                    }
+                break;
+            }
+        }
+        return results;
+    },
+    
+    /**
+     * Method: isValidType
+     * Check if a GeoJSON object is a valid representative of the given type.
+     *
+     * Returns:
+     * {Boolean} The object is valid GeoJSON object of the given type.
+     */
+    isValidType: function(obj, type) {
+        var valid = false;
+        switch(type) {
+            case "Geometry":
+                if(OpenLayers.Util.indexOf(
+                    ["Point", "MultiPoint", "LineString", "MultiLineString",
+                     "Polygon", "MultiPolygon", "Box", "GeometryCollection"],
+                    obj.type) == -1) {
+                    // unsupported geometry type
+                    OpenLayers.Console.error("Unsupported geometry type: " +
+                                              obj.type);
+                } else {
+                    valid = true;
+                }
+                break;
+            case "FeatureCollection":
+                // allow for any type to be converted to a feature collection
+                valid = true;
+                break;
+            default:
+                // for Feature types must match
+                if(obj.type == type) {
+                    valid = true;
+                } else {
+                    OpenLayers.Console.error("Cannot convert types from " +
+                                              obj.type + " to " + type);
+                }
+        }
+        return valid;
+    },
+    
+    /**
+     * Method: parseFeature
+     * Convert a feature object from GeoJSON into an
+     *     <OpenLayers.Feature.Vector>.
+     *
+     * Parameters:
+     * obj - {Object} An object created from a GeoJSON object
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A feature.
+     */
+    parseFeature: function(obj) {
+        var feature, geometry, attributes;
+        attributes = (obj.properties) ? obj.properties : {};
+        try {
+            geometry = this.parseGeometry(obj.geometry);            
+        } catch(err) {
+            // deal with bad geometries
+            throw err;
+        }
+        feature = new OpenLayers.Feature.Vector(geometry, attributes);
+        if(obj.id) {
+            feature.fid = obj.id;
+        }
+        return feature;
+    },
+    
+    /**
+     * Method: parseGeometry
+     * Convert a geometry object from GeoJSON into an <OpenLayers.Geometry>.
+     *
+     * Parameters:
+     * obj - {Object} An object created from a GeoJSON object
+     *
+     * Returns: 
+     * {<OpenLayers.Geometry>} A geometry.
+     */
+    parseGeometry: function(obj) {
+        if (obj == null) {
+            return null;
+        }
+        var geometry;
+        if(obj.type == "GeometryCollection") {
+            if(!(obj.geometries instanceof Array)) {
+                throw "GeometryCollection must have geometries array: " + obj;
+            }
+            var numGeom = obj.geometries.length;
+            var components = new Array(numGeom);
+            for(var i=0; i<numGeom; ++i) {
+                components[i] = this.parseGeometry.apply(
+                    this, [obj.geometries[i]]
+                );
+            }
+            geometry = new OpenLayers.Geometry.Collection(components);
+        } else {
+            if(!(obj.coordinates instanceof Array)) {
+                throw "Geometry must have coordinates array: " + obj;
+            }
+            if(!this.parseCoords[obj.type.toLowerCase()]) {
+                throw "Unsupported geometry type: " + obj.type;
+            }
+            try {
+                geometry = this.parseCoords[obj.type.toLowerCase()].apply(
+                    this, [obj.coordinates]
+                );
+            } catch(err) {
+                // deal with bad coordinates
+                throw err;
+            }
+        }
+        if (this.internalProjection && this.externalProjection) {
+            geometry.transform(this.externalProjection, 
+                               this.internalProjection); 
+        }                       
+        return geometry;
+    },
+    
+    /**
+     * Property: parseCoords
+     * Object with properties corresponding to the GeoJSON geometry types.
+     *     Property values are functions that do the actual parsing.
+     */
+    parseCoords: {
+        /**
+         * Method: parseCoords.point
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array - {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "point": function(array) {
+            if(array.length != 2) {
+                throw "Only 2D points are supported: " + array;
+            }
+            return new OpenLayers.Geometry.Point(array[0], array[1]);
+        },
+        
+        /**
+         * Method: parseCoords.multipoint
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "multipoint": function(array) {
+            var points = [];
+            var p = null;
+            for(var i=0, len=array.length; i<len; ++i) {
+                try {
+                    p = this.parseCoords["point"].apply(this, [array[i]]);
+                } catch(err) {
+                    throw err;
+                }
+                points.push(p);
+            }
+            return new OpenLayers.Geometry.MultiPoint(points);
+        },
+
+        /**
+         * Method: parseCoords.linestring
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array - {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "linestring": function(array) {
+            var points = [];
+            var p = null;
+            for(var i=0, len=array.length; i<len; ++i) {
+                try {
+                    p = this.parseCoords["point"].apply(this, [array[i]]);
+                } catch(err) {
+                    throw err;
+                }
+                points.push(p);
+            }
+            return new OpenLayers.Geometry.LineString(points);
+        },
+        
+        /**
+         * Method: parseCoords.multilinestring
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array - {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "multilinestring": function(array) {
+            var lines = [];
+            var l = null;
+            for(var i=0, len=array.length; i<len; ++i) {
+                try {
+                    l = this.parseCoords["linestring"].apply(this, [array[i]]);
+                } catch(err) {
+                    throw err;
+                }
+                lines.push(l);
+            }
+            return new OpenLayers.Geometry.MultiLineString(lines);
+        },
+        
+        /**
+         * Method: parseCoords.polygon
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "polygon": function(array) {
+            var rings = [];
+            var r, l;
+            for(var i=0, len=array.length; i<len; ++i) {
+                try {
+                    l = this.parseCoords["linestring"].apply(this, [array[i]]);
+                } catch(err) {
+                    throw err;
+                }
+                r = new OpenLayers.Geometry.LinearRing(l.components);
+                rings.push(r);
+            }
+            return new OpenLayers.Geometry.Polygon(rings);
+        },
+
+        /**
+         * Method: parseCoords.multipolygon
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array - {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "multipolygon": function(array) {
+            var polys = [];
+            var p = null;
+            for(var i=0, len=array.length; i<len; ++i) {
+                try {
+                    p = this.parseCoords["polygon"].apply(this, [array[i]]);
+                } catch(err) {
+                    throw err;
+                }
+                polys.push(p);
+            }
+            return new OpenLayers.Geometry.MultiPolygon(polys);
+        },
+
+        /**
+         * Method: parseCoords.box
+         * Convert a coordinate array from GeoJSON into an
+         *     <OpenLayers.Geometry>.
+         *
+         * Parameters:
+         * array - {Object} The coordinates array from the GeoJSON fragment.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry>} A geometry.
+         */
+        "box": function(array) {
+            if(array.length != 2) {
+                throw "GeoJSON box coordinates must have 2 elements";
+            }
+            return new OpenLayers.Geometry.Polygon([
+                new OpenLayers.Geometry.LinearRing([
+                    new OpenLayers.Geometry.Point(array[0][0], array[0][1]),
+                    new OpenLayers.Geometry.Point(array[1][0], array[0][1]),
+                    new OpenLayers.Geometry.Point(array[1][0], array[1][1]),
+                    new OpenLayers.Geometry.Point(array[0][0], array[1][1]),
+                    new OpenLayers.Geometry.Point(array[0][0], array[0][1])
+                ])
+            ]);
+        }
+
+    },
+
+    /**
+     * APIMethod: write
+     * Serialize a feature, geometry, array of features into a GeoJSON string.
+     *
+     * Parameters:
+     * obj - {Object} An <OpenLayers.Feature.Vector>, <OpenLayers.Geometry>,
+     *     or an array of features.
+     * pretty - {Boolean} Structure the output with newlines and indentation.
+     *     Default is false.
+     *
+     * Returns:
+     * {String} The GeoJSON string representation of the input geometry,
+     *     features, or array of features.
+     */
+    write: function(obj, pretty) {
+        var geojson = {
+            "type": null
+        };
+        if(obj instanceof Array) {
+            geojson.type = "FeatureCollection";
+            var numFeatures = obj.length;
+            geojson.features = new Array(numFeatures);
+            for(var i=0; i<numFeatures; ++i) {
+                var element = obj[i];
+                if(!element instanceof OpenLayers.Feature.Vector) {
+                    var msg = "FeatureCollection only supports collections " +
+                              "of features: " + element;
+                    throw msg;
+                }
+                geojson.features[i] = this.extract.feature.apply(
+                    this, [element]
+                );
+            }
+        } else if (obj.CLASS_NAME.indexOf("OpenLayers.Geometry") == 0) {
+            geojson = this.extract.geometry.apply(this, [obj]);
+        } else if (obj instanceof OpenLayers.Feature.Vector) {
+            geojson = this.extract.feature.apply(this, [obj]);
+            if(obj.layer && obj.layer.projection) {
+                geojson.crs = this.createCRSObject(obj);
+            }
+        }
+        return OpenLayers.Format.JSON.prototype.write.apply(this,
+                                                            [geojson, pretty]);
+    },
+
+    /**
+     * Method: createCRSObject
+     * Create the CRS object for an object.
+     *
+     * Parameters:
+     * object - {<OpenLayers.Feature.Vector>} 
+     *
+     * Returns:
+     * {Object} An object which can be assigned to the crs property
+     * of a GeoJSON object.
+     */
+    createCRSObject: function(object) {
+       var proj = object.layer.projection.toString();
+       var crs = {};
+       if (proj.match(/epsg:/i)) {
+           var code = parseInt(proj.substring(proj.indexOf(":") + 1));
+           if (code == 4326) {
+               crs = {
+                   "type": "OGC",
+                   "properties": {
+                       "urn": "urn:ogc:def:crs:OGC:1.3:CRS84"
+                   }
+               };
+           } else {    
+               crs = {
+                   "type": "EPSG",
+                   "properties": {
+                       "code": code 
+                   }
+               };
+           }    
+       }
+       return crs;
+    },
+    
+    /**
+     * Property: extract
+     * Object with properties corresponding to the GeoJSON types.
+     *     Property values are functions that do the actual value extraction.
+     */
+    extract: {
+        /**
+         * Method: extract.feature
+         * Return a partial GeoJSON object representing a single feature.
+         *
+         * Parameters:
+         * feature - {<OpenLayers.Feature.Vector>}
+         *
+         * Returns:
+         * {Object} An object representing the point.
+         */
+        'feature': function(feature) {
+            var geom = this.extract.geometry.apply(this, [feature.geometry]);
+            return {
+                "type": "Feature",
+                "id": feature.fid == null ? feature.id : feature.fid,
+                "properties": feature.attributes,
+                "geometry": geom
+            };
+        },
+        
+        /**
+         * Method: extract.geometry
+         * Return a GeoJSON object representing a single geometry.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry>}
+         *
+         * Returns:
+         * {Object} An object representing the geometry.
+         */
+        'geometry': function(geometry) {
+            if (geometry == null) {
+                return null;
+            }
+            if (this.internalProjection && this.externalProjection) {
+                geometry = geometry.clone();
+                geometry.transform(this.internalProjection, 
+                                   this.externalProjection);
+            }                       
+            var geometryType = geometry.CLASS_NAME.split('.')[2];
+            var data = this.extract[geometryType.toLowerCase()].apply(this, [geometry]);
+            var json;
+            if(geometryType == "Collection") {
+                json = {
+                    "type": "GeometryCollection",
+                    "geometries": data
+                };
+            } else {
+                json = {
+                    "type": geometryType,
+                    "coordinates": data
+                };
+            }
+            
+            return json;
+        },
+
+        /**
+         * Method: extract.point
+         * Return an array of coordinates from a point.
+         *
+         * Parameters:
+         * point - {<OpenLayers.Geometry.Point>}
+         *
+         * Returns: 
+         * {Array} An array of coordinates representing the point.
+         */
+        'point': function(point) {
+            return [point.x, point.y];
+        },
+
+        /**
+         * Method: extract.multipoint
+         * Return an array of point coordinates from a multipoint.
+         *
+         * Parameters:
+         * multipoint - {<OpenLayers.Geometry.MultiPoint>}
+         *
+         * Returns:
+         * {Array} An array of point coordinate arrays representing
+         *     the multipoint.
+         */
+        'multipoint': function(multipoint) {
+            var array = [];
+            for(var i=0, len=multipoint.components.length; i<len; ++i) {
+                array.push(this.extract.point.apply(this, [multipoint.components[i]]));
+            }
+            return array;
+        },
+        
+        /**
+         * Method: extract.linestring
+         * Return an array of coordinate arrays from a linestring.
+         *
+         * Parameters:
+         * linestring - {<OpenLayers.Geometry.LineString>}
+         *
+         * Returns:
+         * {Array} An array of coordinate arrays representing
+         *     the linestring.
+         */
+        'linestring': function(linestring) {
+            var array = [];
+            for(var i=0, len=linestring.components.length; i<len; ++i) {
+                array.push(this.extract.point.apply(this, [linestring.components[i]]));
+            }
+            return array;
+        },
+
+        /**
+         * Method: extract.multilinestring
+         * Return an array of linestring arrays from a linestring.
+         * 
+         * Parameters:
+         * linestring - {<OpenLayers.Geometry.MultiLineString>}
+         * 
+         * Returns:
+         * {Array} An array of linestring arrays representing
+         *     the multilinestring.
+         */
+        'multilinestring': function(multilinestring) {
+            var array = [];
+            for(var i=0, len=multilinestring.components.length; i<len; ++i) {
+                array.push(this.extract.linestring.apply(this, [multilinestring.components[i]]));
+            }
+            return array;
+        },
+        
+        /**
+         * Method: extract.polygon
+         * Return an array of linear ring arrays from a polygon.
+         *
+         * Parameters:
+         * polygon - {<OpenLayers.Geometry.Polygon>}
+         * 
+         * Returns:
+         * {Array} An array of linear ring arrays representing the polygon.
+         */
+        'polygon': function(polygon) {
+            var array = [];
+            for(var i=0, len=polygon.components.length; i<len; ++i) {
+                array.push(this.extract.linestring.apply(this, [polygon.components[i]]));
+            }
+            return array;
+        },
+
+        /**
+         * Method: extract.multipolygon
+         * Return an array of polygon arrays from a multipolygon.
+         * 
+         * Parameters:
+         * multipolygon - {<OpenLayers.Geometry.MultiPolygon>}
+         * 
+         * Returns:
+         * {Array} An array of polygon arrays representing
+         *     the multipolygon
+         */
+        'multipolygon': function(multipolygon) {
+            var array = [];
+            for(var i=0, len=multipolygon.components.length; i<len; ++i) {
+                array.push(this.extract.polygon.apply(this, [multipolygon.components[i]]));
+            }
+            return array;
+        },
+        
+        /**
+         * Method: extract.collection
+         * Return an array of geometries from a geometry collection.
+         * 
+         * Parameters:
+         * collection - {<OpenLayers.Geometry.Collection>}
+         * 
+         * Returns:
+         * {Array} An array of geometry objects representing the geometry
+         *     collection.
+         */
+        'collection': function(collection) {
+            var len = collection.components.length;
+            var array = new Array(len);
+            for(var i=0; i<len; ++i) {
+                array[i] = this.extract.geometry.apply(
+                    this, [collection.components[i]]
+                );
+            }
+            return array;
+        }
+        
+
+    },
+
+    CLASS_NAME: "OpenLayers.Format.GeoJSON" 
+
+});     
+/* ======================================================================
+    OpenLayers/Format/GeoRSS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Format.GeoRSS
+ * Read/write GeoRSS parser. Create a new instance with the 
+ *     <OpenLayers.Format.GeoRSS> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.GeoRSS = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * APIProperty: rssns
+     * {String} RSS namespace to use. Defaults to
+     *   "http://backend.userland.com/rss2"
+     */
+    rssns: "http://backend.userland.com/rss2",
+    
+    /**
+     * APIProperty: featurens
+     * {String} Feature Attributes namespace.  Defaults to
+     *    "http://mapserver.gis.umn.edu/mapserver"
+     */
+    featureNS: "http://mapserver.gis.umn.edu/mapserver",
+    
+    /**
+     * APIProperty: georssns
+     * {String} GeoRSS namespace to use.  Defaults to
+     *     "http://www.georss.org/georss"
+     */
+    georssns: "http://www.georss.org/georss",
+
+    /**
+     * APIProperty: geons
+     * {String} W3C Geo namespace to use.  Defaults to
+     *     "http://www.w3.org/2003/01/geo/wgs84_pos#"
+     */
+    geons: "http://www.w3.org/2003/01/geo/wgs84_pos#",
+    
+    /**
+     * APIProperty: featureTitle
+     * {String} Default title for features.  Defaults to "Untitled"
+     */
+    featureTitle: "Untitled",
+    
+    /**
+     * APIProperty: featureDescription
+     * {String} Default description for features.  Defaults to "No Description"
+     */
+    featureDescription: "No Description",
+    
+    /**
+     * Property: gmlParse
+     * {Object} GML Format object for parsing features
+     * Non-API and only created if necessary
+     */
+    gmlParser: null,
+
+    /**
+     * APIProperty: xy
+     * {Boolean} Order of the GML coordinate: true:(x,y) or false:(y,x)
+     * For GeoRSS the default is (y,x), therefore: false
+     */ 
+    xy: false,
+    
+    /**
+     * Constructor: OpenLayers.Format.GeoRSS
+     * Create a new parser for GeoRSS.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+    
+    /**
+     * Method: createGeometryFromItem
+     * Return a geometry from a GeoRSS Item.
+     *
+     * Parameters:
+     * item - {DOMElement} A GeoRSS item node.
+     *
+     * Returns:
+     * {<OpenLayers.Geometry>} A geometry representing the node.
+     */
+    createGeometryFromItem: function(item) {
+        var point = this.getElementsByTagNameNS(item, this.georssns, "point");
+        var lat = this.getElementsByTagNameNS(item, this.geons, 'lat');
+        var lon = this.getElementsByTagNameNS(item, this.geons, 'long');
+        
+        var line = this.getElementsByTagNameNS(item,
+                                                this.georssns,
+                                                "line");
+        var polygon = this.getElementsByTagNameNS(item,
+                                                this.georssns,
+                                                "polygon");
+        var where = this.getElementsByTagNameNS(item, 
+                                                this.georssns, 
+                                                "where");
+        if (point.length > 0 || (lat.length > 0 && lon.length > 0)) {
+            var location;
+            if (point.length > 0) {
+                location = OpenLayers.String.trim(
+                                point[0].firstChild.nodeValue).split(/\s+/);
+                if (location.length !=2) {
+                    location = OpenLayers.String.trim(
+                                point[0].firstChild.nodeValue).split(/\s*,\s*/);
+                }
+            } else {
+                location = [parseFloat(lat[0].firstChild.nodeValue),
+                                parseFloat(lon[0].firstChild.nodeValue)];
+            }    
+
+            var geometry = new OpenLayers.Geometry.Point(parseFloat(location[1]),
+                                                         parseFloat(location[0]));
+              
+        } else if (line.length > 0) {
+            var coords = OpenLayers.String.trim(this.concatChildValues(line[0])).split(/\s+/);
+            var components = []; 
+            var point;
+            for (var i=0, len=coords.length; i<len; i+=2) {
+                point = new OpenLayers.Geometry.Point(parseFloat(coords[i+1]), 
+                                                     parseFloat(coords[i]));
+                components.push(point);
+            }
+            geometry = new OpenLayers.Geometry.LineString(components);
+        } else if (polygon.length > 0) { 
+            var coords = OpenLayers.String.trim(this.concatChildValues(polygon[0])).split(/\s+/);
+            var components = []; 
+            var point;
+            for (var i=0, len=coords.length; i<len; i+=2) {
+                point = new OpenLayers.Geometry.Point(parseFloat(coords[i+1]), 
+                                                     parseFloat(coords[i]));
+                components.push(point);
+            }
+            geometry = new OpenLayers.Geometry.Polygon([new OpenLayers.Geometry.LinearRing(components)]);
+        } else if (where.length > 0) { 
+            if (!this.gmlParser) {
+              this.gmlParser = new OpenLayers.Format.GML({'xy': this.xy});
+            }
+            var feature = this.gmlParser.parseFeature(where[0]);
+            geometry = feature.geometry;
+        }
+        
+        if (geometry && this.internalProjection && this.externalProjection) {
+            geometry.transform(this.externalProjection, 
+                               this.internalProjection);
+        }
+
+        return geometry;
+    },        
+
+    /**
+     * Method: createFeatureFromItem
+     * Return a feature from a GeoRSS Item.
+     *
+     * Parameters:
+     * item - {DOMElement} A GeoRSS item node.
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A feature representing the item.
+     */
+    createFeatureFromItem: function(item) {
+        var geometry = this.createGeometryFromItem(item);
+     
+        /* Provide defaults for title and description */
+        var title = this.getChildValue(item, "*", "title", this.featureTitle);
+       
+        /* First try RSS descriptions, then Atom summaries */
+        var description = this.getChildValue(
+            item, "*", "description",
+            this.getChildValue(item, "*", "content", this.featureDescription)
+        );
+
+        /* If no link URL is found in the first child node, try the
+           href attribute */
+        var link = this.getChildValue(item, "*", "link");
+        if(!link) {
+            try {
+                link = this.getElementsByTagNameNS(item, "*", "link")[0].getAttribute("href");
+            } catch(e) {
+                link = null;
+            }
+        }
+
+        var id = this.getChildValue(item, "*", "id", null);
+        
+        var data = {
+            "title": title,
+            "description": description,
+            "link": link
+        };
+        var feature = new OpenLayers.Feature.Vector(geometry, data);
+        feature.fid = id;
+        return feature;
+    },        
+    
+    /**
+     * Method: getChildValue
+     *
+     * Parameters:
+     * node - {DOMElement}
+     * nsuri - {String} Child node namespace uri ("*" for any).
+     * name - {String} Child node name.
+     * def - {String} Optional string default to return if no child found.
+     *
+     * Returns:
+     * {String} The value of the first child with the given tag name.  Returns
+     *     default value or empty string if none found.
+     */
+    getChildValue: function(node, nsuri, name, def) {
+        var value;
+        var eles = this.getElementsByTagNameNS(node, nsuri, name);
+        if(eles && eles[0] && eles[0].firstChild
+            && eles[0].firstChild.nodeValue) {
+            value = eles[0].firstChild.nodeValue;
+        } else {
+            value = (def == undefined) ? "" : def;
+        }
+        return value;
+    },
+    
+    /**
+     * APIMethod: read
+     * Return a list of features from a GeoRSS doc
+     
+     * Parameters:
+     * data - {Element} 
+     *
+     * Returns:
+     * An Array of <OpenLayers.Feature.Vector>s
+     */
+    read: function(doc) {
+        if (typeof doc == "string") { 
+            doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+        }
+
+        /* Try RSS items first, then Atom entries */
+        var itemlist = null;
+        itemlist = this.getElementsByTagNameNS(doc, '*', 'item');
+        if (itemlist.length == 0) {
+            itemlist = this.getElementsByTagNameNS(doc, '*', 'entry');
+        }
+        
+        var numItems = itemlist.length;
+        var features = new Array(numItems);
+        for(var i=0; i<numItems; i++) {
+            features[i] = this.createFeatureFromItem(itemlist[i]);
+        }
+        return features;
+    },
+    
+
+    /**
+     * APIMethod: write
+     * Accept Feature Collection, and return a string. 
+     * 
+     * Parameters: 
+     * features - {Array(<OpenLayers.Feature.Vector>)} List of features to serialize into a string.
+     */
+    write: function(features) {
+        var georss;
+        if(features instanceof Array) {
+            georss = this.createElementNS(this.rssns, "rss");
+            for(var i=0, len=features.length; i<len; i++) {
+                georss.appendChild(this.createFeatureXML(features[i]));
+            }
+        } else {
+            georss = this.createFeatureXML(features);
+        }
+        return OpenLayers.Format.XML.prototype.write.apply(this, [georss]);
+    },
+
+    /**
+     * Method: createFeatureXML
+     * Accept an <OpenLayers.Feature.Vector>, and build a geometry for it.
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     *
+     * Returns:
+     * {DOMElement}
+     */
+    createFeatureXML: function(feature) {
+        var geometryNode = this.buildGeometryNode(feature.geometry);
+        var featureNode = this.createElementNS(this.rssns, "item");
+        var titleNode = this.createElementNS(this.rssns, "title");
+        titleNode.appendChild(this.createTextNode(feature.attributes.title ? feature.attributes.title : ""));
+        var descNode = this.createElementNS(this.rssns, "description");
+        descNode.appendChild(this.createTextNode(feature.attributes.description ? feature.attributes.description : ""));
+        featureNode.appendChild(titleNode);
+        featureNode.appendChild(descNode);
+        if (feature.attributes.link) {
+            var linkNode = this.createElementNS(this.rssns, "link");
+            linkNode.appendChild(this.createTextNode(feature.attributes.link));
+            featureNode.appendChild(linkNode);
+        }    
+        for(var attr in feature.attributes) {
+            if (attr == "link" || attr == "title" || attr == "description") { continue; } 
+            var attrText = this.createTextNode(feature.attributes[attr]); 
+            var nodename = attr;
+            if (attr.search(":") != -1) {
+                nodename = attr.split(":")[1];
+            }    
+            var attrContainer = this.createElementNS(this.featureNS, "feature:"+nodename);
+            attrContainer.appendChild(attrText);
+            featureNode.appendChild(attrContainer);
+        }    
+        featureNode.appendChild(geometryNode);
+        return featureNode;
+    },    
+    
+    /** 
+     * Method: buildGeometryNode
+     * builds a GeoRSS node with a given geometry
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     *
+     * Returns:
+     * {DOMElement} A gml node.
+     */
+    buildGeometryNode: function(geometry) {
+        if (this.internalProjection && this.externalProjection) {
+            geometry = geometry.clone();
+            geometry.transform(this.internalProjection, 
+                               this.externalProjection);
+        }
+        var node;
+        // match Polygon
+        if (geometry.CLASS_NAME == "OpenLayers.Geometry.Polygon") {
+            node = this.createElementNS(this.georssns, 'georss:polygon');
+            
+            node.appendChild(this.buildCoordinatesNode(geometry.components[0]));
+        }
+        // match LineString
+        else if (geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
+            node = this.createElementNS(this.georssns, 'georss:line');
+            
+            node.appendChild(this.buildCoordinatesNode(geometry));
+        }
+        // match Point
+        else if (geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+            node = this.createElementNS(this.georssns, 'georss:point');
+            node.appendChild(this.buildCoordinatesNode(geometry));
+        } else {
+            throw "Couldn't parse " + geometry.CLASS_NAME;
+        }  
+        return node;         
+    },
+    
+    /** 
+     * Method: buildCoordinatesNode
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     */
+    buildCoordinatesNode: function(geometry) {
+        var points = null;
+        
+        if (geometry.components) {
+            points = geometry.components;
+        }
+
+        var path;
+        if (points) {
+            var numPoints = points.length;
+            var parts = new Array(numPoints);
+            for (var i = 0; i < numPoints; i++) {
+                parts[i] = points[i].y + " " + points[i].x;
+            }
+            path = parts.join(" ");
+        } else {
+            path = geometry.y + " " + geometry.x;
+        }
+        return this.createTextNode(path);
+    },
+
+    CLASS_NAME: "OpenLayers.Format.GeoRSS" 
+});     
+/* ======================================================================
+    OpenLayers/Format/KML.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ * @requires OpenLayers/Geometry/Collection.js
+ * @requires OpenLayers/Request/XMLHttpRequest.js
+ */
+
+/**
+ * Class: OpenLayers.Format.KML
+ * Read/Wite KML. Create a new instance with the <OpenLayers.Format.KML>
+ *     constructor. 
+ * 
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * APIProperty: kmlns
+     * {String} KML Namespace to use. Defaults to 2.0 namespace.
+     */
+    kmlns: "http://earth.google.com/kml/2.0",
+    
+    /** 
+     * APIProperty: placemarksDesc
+     * {String} Name of the placemarks.  Default is "No description available."
+     */
+    placemarksDesc: "No description available",
+    
+    /** 
+     * APIProperty: foldersName
+     * {String} Name of the folders.  Default is "OpenLayers export."
+     */
+    foldersName: "OpenLayers export",
+    
+    /** 
+     * APIProperty: foldersDesc
+     * {String} Description of the folders. Default is "Exported on [date]."
+     */
+    foldersDesc: "Exported on " + new Date(),
+    
+    /**
+     * APIProperty: extractAttributes
+     * {Boolean} Extract attributes from KML.  Default is true.
+     *           Extracting styleUrls requires this to be set to true
+     */
+    extractAttributes: true,
+    
+    /**
+     * Property: extractStyles
+     * {Boolean} Extract styles from KML.  Default is false.
+     *           Extracting styleUrls also requires extractAttributes to be
+     *           set to true
+     */
+    extractStyles: false,
+    
+    /**
+     * Property: internalns
+     * {String} KML Namespace to use -- defaults to the namespace of the
+     *     Placemark node being parsed, but falls back to kmlns. 
+     */
+    internalns: null,
+
+    /**
+     * Property: features
+     * {Array} Array of features
+     *     
+     */
+    features: null,
+
+    /**
+     * Property: styles
+     * {Object} Storage of style objects
+     *     
+     */
+    styles: null,
+    
+    /**
+     * Property: styleBaseUrl
+     * {String}
+     */
+    styleBaseUrl: "",
+
+    /**
+     * Property: fetched
+     * {Object} Storage of KML URLs that have been fetched before
+     *     in order to prevent reloading them.
+     */
+    fetched: null,
+
+    /**
+     * APIProperty: maxDepth
+     * {Integer} Maximum depth for recursive loading external KML URLs 
+     *           Defaults to 0: do no external fetching
+     */
+    maxDepth: 0,
+
+    /**
+     * Constructor: OpenLayers.Format.KML
+     * Create a new parser for KML.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        // compile regular expressions once instead of every time they are used
+        this.regExes = {
+            trimSpace: (/^\s*|\s*$/g),
+            removeSpace: (/\s*/g),
+            splitSpace: (/\s+/),
+            trimComma: (/\s*,\s*/g),
+            kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
+            kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
+            straightBracket: (/\$\[(.*?)\]/g)
+        };
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
+    },
+
+    /**
+     * APIMethod: read
+     * Read data from a string, and return a list of features. 
+     * 
+     * Parameters: 
+     * data    - {String} or {DOMElement} data to read/parse.
+     *
+     * Returns:
+     * {Array(<OpenLayers.Feature.Vector>)} List of features.
+     */
+    read: function(data) {
+        this.features = [];
+        this.styles   = {};
+        this.fetched  = {};
+
+        // Set default options 
+        var options = {
+            depth: this.maxDepth,
+            styleBaseUrl: this.styleBaseUrl
+        };
+
+        return this.parseData(data, options);
+    },
+
+    /**
+     * Method: parseData
+     * Read data from a string, and return a list of features. 
+     * 
+     * Parameters: 
+     * data    - {String} or {DOMElement} data to read/parse.
+     * options - {Object} Hash of options
+     *
+     * Returns:
+     * {Array(<OpenLayers.Feature.Vector>)} List of features.
+     */
+    parseData: function(data, options) {
+        if(typeof data == "string") {
+            data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
+        }
+
+        // Loop throught the following node types in this order and
+        // process the nodes found 
+        var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"];
+        for(var i=0, len=types.length; i<len; ++i) {
+            var type = types[i];
+
+            var nodes = this.getElementsByTagNameNS(data, "*", type);
+
+            // skip to next type if no nodes are found
+            if(nodes.length == 0) { 
+                continue;
+            }
+
+            switch (type.toLowerCase()) {
+
+                // Fetch external links 
+                case "link":
+                case "networklink":
+                    this.parseLinks(nodes, options);
+                    break;
+
+                // parse style information
+                case "style":
+                    if (this.extractStyles) {
+                        this.parseStyles(nodes, options);
+                    }
+                    break;
+                case "stylemap":
+                    if (this.extractStyles) {
+                        this.parseStyleMaps(nodes, options);
+                    }
+                    break;
+
+                // parse features
+                case "placemark":
+                    this.parseFeatures(nodes, options);
+                    break;
+            }
+        }
+        
+        return this.features;
+    },
+
+    /**
+     * Method: parseLinks
+     * Finds URLs of linked KML documents and fetches them
+     * 
+     * Parameters: 
+     * nodes   - {Array} of {DOMElement} data to read/parse.
+     * options - {Object} Hash of options
+     * 
+     */
+    parseLinks: function(nodes, options) {
+        
+        // Fetch external links <NetworkLink> and <Link>
+        // Don't do anything if we have reached our maximum depth for recursion
+        if (options.depth >= this.maxDepth) {
+            return false;
+        }
+
+        // increase depth
+        var newOptions = OpenLayers.Util.extend({}, options);
+        newOptions.depth++;
+
+        for(var i=0, len=nodes.length; i<len; i++) {
+            var href = this.parseProperty(nodes[i], "*", "href");
+            if(href && !this.fetched[href]) {
+                this.fetched[href] = true; // prevent reloading the same urls
+                var data = this.fetchLink(href);
+                if (data) {
+                    this.parseData(data, newOptions);
+                }
+            } 
+        }
+
+    },
+
+    /**
+     * Method: fetchLink
+     * Fetches a URL and returns the result
+     * 
+     * Parameters: 
+     * href  - {String} url to be fetched
+     * 
+     */
+    fetchLink: function(href) {
+        var request = OpenLayers.Request.GET({url: href, async: false});
+        if (request) {
+            return request.responseText;
+        }
+    },
+
+    /**
+     * Method: parseStyles
+     * Looks for <Style> nodes in the data and parses them
+     * Also parses <StyleMap> nodes, but only uses the 'normal' key
+     * 
+     * Parameters: 
+     * nodes    - {Array} of {DOMElement} data to read/parse.
+     * options  - {Object} Hash of options
+     * 
+     */
+    parseStyles: function(nodes, options) {
+        for(var i=0, len=nodes.length; i<len; i++) {
+            var style = this.parseStyle(nodes[i]);
+            if(style) {
+                styleName = (options.styleBaseUrl || "") + "#" + style.id;
+                
+                this.styles[styleName] = style;
+            }
+        }
+    },
+
+    /**
+     * Method: parseStyle
+     * Parses the children of a <Style> node and builds the style hash
+     * accordingly
+     * 
+     * Parameters: 
+     * node - {DOMElement} <Style> node
+     * 
+     */
+    parseStyle: function(node) {
+        var style = {};
+        
+        var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle"];
+        var type, nodeList, geometry, parser;
+        for(var i=0, len=types.length; i<len; ++i) {
+            type = types[i];
+            styleTypeNode = this.getElementsByTagNameNS(node, 
+                                                   "*", type)[0];
+            if(!styleTypeNode) { 
+                continue;
+            }
+
+            // only deal with first geometry of this type
+            switch (type.toLowerCase()) {
+                case "linestyle":
+                    var color = this.parseProperty(styleTypeNode, "*", "color");
+                    if (color) {
+                        var matches = (color.toString()).match(
+                                                         this.regExes.kmlColor);
+
+                        // transparency
+                        var alpha = matches[1];
+                        style["strokeOpacity"] = parseInt(alpha, 16) / 255;
+
+                        // rgb colors (google uses bgr)
+                        var b = matches[2]; 
+                        var g = matches[3]; 
+                        var r = matches[4]; 
+                        style["strokeColor"] = "#" + r + g + b;
+                    }
+                    
+                    var width = this.parseProperty(styleTypeNode, "*", "width");
+                    if (width) {
+                        style["strokeWidth"] = width;
+                    }
+
+                case "polystyle":
+                    var color = this.parseProperty(styleTypeNode, "*", "color");
+                    if (color) {
+                        var matches = (color.toString()).match(
+                                                         this.regExes.kmlColor);
+
+                        // transparency
+                        var alpha = matches[1];
+                        style["fillOpacity"] = parseInt(alpha, 16) / 255;
+
+                        // rgb colors (google uses bgr)
+                        var b = matches[2]; 
+                        var g = matches[3]; 
+                        var r = matches[4]; 
+                        style["fillColor"] = "#" + r + g + b;
+                    }
+                    
+                    break;
+                case "iconstyle":
+                    // set scale
+                    var scale = parseFloat(this.parseProperty(styleTypeNode, 
+                                                          "*", "scale") || 1);
+  
+                    // set default width and height of icon
+                    var width = 32 * scale;
+                    var height = 32 * scale;
+
+                    var iconNode = this.getElementsByTagNameNS(styleTypeNode, 
+                                               "*", 
+                                               "Icon")[0];
+                    if (iconNode) {
+                        var href = this.parseProperty(iconNode, "*", "href");
+                        if (href) {                                                   
+
+                            var w = this.parseProperty(iconNode, "*", "w");
+                            var h = this.parseProperty(iconNode, "*", "h");
+
+                            // Settings for Google specific icons that are 64x64
+                            // We set the width and height to 64 and halve the
+                            // scale to prevent icons from being too big
+                            var google = "http://maps.google.com/mapfiles/kml";
+                            if (OpenLayers.String.startsWith(
+                                                 href, google) && !w && !h) {
+                                w = 64;
+                                h = 64;
+                                scale = scale / 2;
+                            }
+                                
+                            // if only dimension is defined, make sure the
+                            // other one has the same value
+                            w = w || h;
+                            h = h || w;
+
+                            if (w) {
+                                width = parseInt(w) * scale;
+                            }
+
+                            if (h) {
+                                height = parseInt(h) * scale;
+                            }
+
+                            // support for internal icons 
+                            //    (/root://icons/palette-x.png)
+                            // x and y tell the position on the palette:
+                            // - in pixels
+                            // - starting from the left bottom
+                            // We translate that to a position in the list 
+                            // and request the appropriate icon from the 
+                            // google maps website
+                            var matches = href.match(this.regExes.kmlIconPalette);
+                            if (matches)  {
+                                var palette = matches[1];
+                                var file_extension = matches[2];
+
+                                var x = this.parseProperty(iconNode, "*", "x");
+                                var y = this.parseProperty(iconNode, "*", "y");
+
+                                var posX = x ? x/32 : 0;
+                                var posY = y ? (7 - y/32) : 7;
+
+                                var pos = posY * 8 + posX;
+                                href = "http://maps.google.com/mapfiles/kml/pal" 
+                                     + palette + "/icon" + pos + file_extension;
+                            }
+
+                            style["graphicOpacity"] = 1; // fully opaque
+                            style["externalGraphic"] = href;
+                        }
+
+                    }
+
+
+                    // hotSpots define the offset for an Icon
+                    var hotSpotNode = this.getElementsByTagNameNS(styleTypeNode, 
+                                               "*", 
+                                               "hotSpot")[0];
+                    if (hotSpotNode) {
+                        var x = parseFloat(hotSpotNode.getAttribute("x"));
+                        var y = parseFloat(hotSpotNode.getAttribute("y"));
+
+                        var xUnits = hotSpotNode.getAttribute("xunits");
+                        if (xUnits == "pixels") {
+                            style["graphicXOffset"] = -x * scale;
+                        }
+                        else if (xUnits == "insetPixels") {
+                            style["graphicXOffset"] = -width + (x * scale);
+                        }
+                        else if (xUnits == "fraction") {
+                            style["graphicXOffset"] = -width * x;
+                        }
+
+                        var yUnits = hotSpotNode.getAttribute("yunits");
+                        if (yUnits == "pixels") {
+                            style["graphicYOffset"] = -height + (y * scale) + 1;
+                        }
+                        else if (yUnits == "insetPixels") {
+                            style["graphicYOffset"] = -(y * scale) + 1;
+                        }
+                        else if (yUnits == "fraction") {
+                            style["graphicYOffset"] =  -height * (1 - y) + 1;
+                        }
+                    }
+
+                    style["graphicWidth"] = width;
+                    style["graphicHeight"] = height;
+                    break;
+
+                case "balloonstyle":
+                    var balloonStyle = OpenLayers.Util.getXmlNodeValue(
+                                            styleTypeNode);
+                    if (balloonStyle) {
+                        style["balloonStyle"] = balloonStyle.replace(
+                                       this.regExes.straightBracket, "${$1}");
+                    }
+                    break;
+                default:
+            }
+        }
+
+        // Some polygons have no line color, so we use the fillColor for that
+        if (!style["strokeColor"] && style["fillColor"]) {
+            style["strokeColor"] = style["fillColor"];
+        }
+
+        var id = node.getAttribute("id");
+        if (id && style) {
+            style.id = id;
+        }
+
+        return style;
+    },
+
+    /**
+     * Method: parseStyleMaps
+     * Looks for <Style> nodes in the data and parses them
+     * Also parses <StyleMap> nodes, but only uses the 'normal' key
+     * 
+     * Parameters: 
+     * nodes    - {Array} of {DOMElement} data to read/parse.
+     * options  - {Object} Hash of options
+     * 
+     */
+    parseStyleMaps: function(nodes, options) {
+        // Only the default or "normal" part of the StyleMap is processed now
+        // To do the select or "highlight" bit, we'd need to change lots more
+
+        for(var i=0, len=nodes.length; i<len; i++) {
+            var node = nodes[i];
+            var pairs = this.getElementsByTagNameNS(node, "*", 
+                            "Pair");
+
+            var id = node.getAttribute("id");
+            for (var j=0, jlen=pairs.length; j<jlen; j++) {
+                var pair = pairs[j];
+                // Use the shortcut in the SLD format to quickly retrieve the 
+                // value of a node. Maybe it's good to have a method in 
+                // Format.XML to do this
+                var key = this.parseProperty(pair, "*", "key");
+                var styleUrl = this.parseProperty(pair, "*", "styleUrl");
+
+                if (styleUrl && key == "normal") {
+                    this.styles[(options.styleBaseUrl || "") + "#" + id] =
+                        this.styles[(options.styleBaseUrl || "") + styleUrl];
+                }
+
+                if (styleUrl && key == "highlight") {
+                    // TODO: implement the "select" part
+                }
+
+            }
+        }
+
+    },
+
+
+    /**
+     * Method: parseFeatures
+     * Loop through all Placemark nodes and parse them.
+     * Will create a list of features
+     * 
+     * Parameters: 
+     * nodes    - {Array} of {DOMElement} data to read/parse.
+     * options  - {Object} Hash of options
+     * 
+     */
+    parseFeatures: function(nodes, options) {
+        var features = new Array(nodes.length);
+        for(var i=0, len=nodes.length; i<len; i++) {
+            var featureNode = nodes[i];
+            var feature = this.parseFeature.apply(this,[featureNode]) ;
+            if(feature) {
+
+                // Create reference to styleUrl 
+                if (this.extractStyles && feature.attributes &&
+                    feature.attributes.styleUrl) {
+                    feature.style = this.getStyle(feature.attributes.styleUrl);
+                }
+
+                if (this.extractStyles) {
+                    // Make sure that <Style> nodes within a placemark are 
+                    // processed as well
+                    var inlineStyleNode = this.getElementsByTagNameNS(featureNode,
+                                                        "*",
+                                                        "Style")[0];
+                    if (inlineStyleNode) {
+                        var inlineStyle= this.parseStyle(inlineStyleNode);
+                        if (inlineStyle) {
+                            feature.style = OpenLayers.Util.extend(
+                                feature.style, inlineStyle
+                            );
+                        }
+                    }
+                }
+
+                // add feature to list of features
+                features[i] = feature;
+            } else {
+                throw "Bad Placemark: " + i;
+            }
+        }
+
+        // add new features to existing feature list
+        this.features = this.features.concat(features);
+    },
+
+    /**
+     * Method: parseFeature
+     * This function is the core of the KML parsing code in OpenLayers.
+     *     It creates the geometries that are then attached to the returned
+     *     feature, and calls parseAttributes() to get attribute data out.
+     *
+     * Parameters:
+     * node - {DOMElement}
+     *
+     * Returns:
+     * {<OpenLayers.Feature.Vector>} A vector feature.
+     */
+    parseFeature: function(node) {
+        // only accept one geometry per feature - look for highest "order"
+        var order = ["MultiGeometry", "Polygon", "LineString", "Point"];
+        var type, nodeList, geometry, parser;
+        for(var i=0, len=order.length; i<len; ++i) {
+            type = order[i];
+            this.internalns = node.namespaceURI ? 
+                    node.namespaceURI : this.kmlns;
+            nodeList = this.getElementsByTagNameNS(node, 
+                                                   this.internalns, type);
+            if(nodeList.length > 0) {
+                // only deal with first geometry of this type
+                var parser = this.parseGeometry[type.toLowerCase()];
+                if(parser) {
+                    geometry = parser.apply(this, [nodeList[0]]);
+                    if (this.internalProjection && this.externalProjection) {
+                        geometry.transform(this.externalProjection, 
+                                           this.internalProjection); 
+                    }                       
+                } else {
+                    OpenLayers.Console.error(OpenLayers.i18n(
+                                "unsupportedGeometryType", {'geomType':type}));
+                }
+                // stop looking for different geometry types
+                break;
+            }
+        }
+
+        // construct feature (optionally with attributes)
+        var attributes;
+        if(this.extractAttributes) {
+            attributes = this.parseAttributes(node);
+        }
+        var feature = new OpenLayers.Feature.Vector(geometry, attributes);
+
+        var fid = node.getAttribute("id") || node.getAttribute("name");
+        if(fid != null) {
+            feature.fid = fid;
+        }
+
+        return feature;
+    },        
+    
+    /**
+     * Method: getStyle
+     * Retrieves a style from a style hash using styleUrl as the key
+     * If the styleUrl doesn't exist yet, we try to fetch it 
+     * Internet
+     * 
+     * Parameters: 
+     * styleUrl  - {String} URL of style
+     * options   - {Object} Hash of options 
+     *
+     * Returns:
+     * {Object}  - (reference to) Style hash
+     */
+    getStyle: function(styleUrl, options) {
+
+        var styleBaseUrl = OpenLayers.Util.removeTail(styleUrl);
+
+        var newOptions = OpenLayers.Util.extend({}, options);
+        newOptions.depth++;
+        newOptions.styleBaseUrl = styleBaseUrl;
+
+        // Fetch remote Style URLs (if not fetched before) 
+        if (!this.styles[styleUrl] 
+                && !OpenLayers.String.startsWith(styleUrl, "#") 
+                && newOptions.depth <= this.maxDepth
+                && !this.fetched[styleBaseUrl] ) {
+
+            var data = this.fetchLink(styleBaseUrl);
+            if (data) {
+                this.parseData(data, newOptions);
+            }
+
+        }
+
+        // return requested style
+        var style = this.styles[styleUrl];
+        return style;
+    },
+    
+    /**
+     * Property: parseGeometry
+     * Properties of this object are the functions that parse geometries based
+     *     on their type.
+     */
+    parseGeometry: {
+        
+        /**
+         * Method: parseGeometry.point
+         * Given a KML node representing a point geometry, create an OpenLayers
+         *     point geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML Point node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Point>} A point geometry.
+         */
+        point: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+                                                       "coordinates");
+            var coords = [];
+            if(nodeList.length > 0) {
+                var coordString = nodeList[0].firstChild.nodeValue;
+                coordString = coordString.replace(this.regExes.removeSpace, "");
+                coords = coordString.split(",");
+            }
+
+            var point = null;
+            if(coords.length > 1) {
+                // preserve third dimension
+                if(coords.length == 2) {
+                    coords[2] = null;
+                }
+                point = new OpenLayers.Geometry.Point(coords[0], coords[1],
+                                                      coords[2]);
+            } else {
+                throw "Bad coordinate string: " + coordString;
+            }
+            return point;
+        },
+        
+        /**
+         * Method: parseGeometry.linestring
+         * Given a KML node representing a linestring geometry, create an
+         *     OpenLayers linestring geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML LineString node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.LineString>} A linestring geometry.
+         */
+        linestring: function(node, ring) {
+            var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+                                                       "coordinates");
+            var line = null;
+            if(nodeList.length > 0) {
+                var coordString = this.concatChildValues(nodeList[0]);
+
+                coordString = coordString.replace(this.regExes.trimSpace,
+                                                  "");
+                coordString = coordString.replace(this.regExes.trimComma,
+                                                  ",");
+                var pointList = coordString.split(this.regExes.splitSpace);
+                var numPoints = pointList.length;
+                var points = new Array(numPoints);
+                var coords, numCoords;
+                for(var i=0; i<numPoints; ++i) {
+                    coords = pointList[i].split(",");
+                    numCoords = coords.length;
+                    if(numCoords > 1) {
+                        if(coords.length == 2) {
+                            coords[2] = null;
+                        }
+                        points[i] = new OpenLayers.Geometry.Point(coords[0],
+                                                                  coords[1],
+                                                                  coords[2]);
+                    } else {
+                        throw "Bad LineString point coordinates: " +
+                              pointList[i];
+                    }
+                }
+                if(numPoints) {
+                    if(ring) {
+                        line = new OpenLayers.Geometry.LinearRing(points);
+                    } else {
+                        line = new OpenLayers.Geometry.LineString(points);
+                    }
+                } else {
+                    throw "Bad LineString coordinates: " + coordString;
+                }
+            }
+
+            return line;
+        },
+        
+        /**
+         * Method: parseGeometry.polygon
+         * Given a KML node representing a polygon geometry, create an
+         *     OpenLayers polygon geometry.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML Polygon node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+         */
+        polygon: function(node) {
+            var nodeList = this.getElementsByTagNameNS(node, this.internalns,
+                                                       "LinearRing");
+            var numRings = nodeList.length;
+            var components = new Array(numRings);
+            if(numRings > 0) {
+                // this assumes exterior ring first, inner rings after
+                var ring;
+                for(var i=0, len=nodeList.length; i<len; ++i) {
+                    ring = this.parseGeometry.linestring.apply(this,
+                                                        [nodeList[i], true]);
+                    if(ring) {
+                        components[i] = ring;
+                    } else {
+                        throw "Bad LinearRing geometry: " + i;
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.Polygon(components);
+        },
+        
+        /**
+         * Method: parseGeometry.multigeometry
+         * Given a KML node representing a multigeometry, create an
+         *     OpenLayers geometry collection.
+         *
+         * Parameters:
+         * node - {DOMElement} A KML MultiGeometry node.
+         *
+         * Returns:
+         * {<OpenLayers.Geometry.Collection>} A geometry collection.
+         */
+        multigeometry: function(node) {
+            var child, parser;
+            var parts = [];
+            var children = node.childNodes;
+            for(var i=0, len=children.length; i<len; ++i ) {
+                child = children[i];
+                if(child.nodeType == 1) {
+                    var type = (child.prefix) ?
+                            child.nodeName.split(":")[1] :
+                            child.nodeName;
+                    var parser = this.parseGeometry[type.toLowerCase()];
+                    if(parser) {
+                        parts.push(parser.apply(this, [child]));
+                    }
+                }
+            }
+            return new OpenLayers.Geometry.Collection(parts);
+        }
+        
+    },
+
+    /**
+     * Method: parseAttributes
+     *
+     * Parameters:
+     * node - {DOMElement}
+     *
+     * Returns:
+     * {Object} An attributes object.
+     */
+    parseAttributes: function(node) {
+        var attributes = {};
+        // assume attribute nodes are type 1 children with a type 3 or 4 child
+        var child, grandchildren, grandchild;
+        var children = node.childNodes;
+        for(var i=0, len=children.length; i<len; ++i) {
+            child = children[i];
+            if(child.nodeType == 1) {
+                grandchildren = child.childNodes;
+                if(grandchildren.length == 1 || grandchildren.length == 3) {
+                    var grandchild;
+                    switch (grandchildren.length) {
+                        case 1:
+                            grandchild = grandchildren[0];
+                            break;
+                        case 3:
+                        default:
+                            grandchild = grandchildren[1];
+                            break;
+                    }
+                    if(grandchild.nodeType == 3 || grandchild.nodeType == 4) {
+                        var name = (child.prefix) ?
+                                child.nodeName.split(":")[1] :
+                                child.nodeName;
+                        var value = OpenLayers.Util.getXmlNodeValue(grandchild);
+                        if (value) {
+                            value = value.replace(this.regExes.trimSpace, "");
+                            attributes[name] = value;
+                        }
+                    }
+                } 
+            }
+        }
+        return attributes;
+    },
+
+    
+    /**
+     * Method: parseProperty
+     * Convenience method to find a node and return its value
+     *
+     * Parameters:
+     * xmlNode    - {<DOMElement>}
+     * namespace  - {String} namespace of the node to find
+     * tagName    - {String} name of the property to parse
+     * 
+     * Returns:
+     * {String} The value for the requested property (defaults to null)
+     */    
+    parseProperty: function(xmlNode, namespace, tagName) {
+        var value;
+        var nodeList = this.getElementsByTagNameNS(xmlNode, namespace, tagName);
+        try {
+            value = OpenLayers.Util.getXmlNodeValue(nodeList[0]);
+        } catch(e) {
+            value = null;
+        }
+     
+        return value;
+    },                                                              
+
+    /**
+     * APIMethod: write
+     * Accept Feature Collection, and return a string. 
+     * 
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>} An array of features.
+     *
+     * Returns:
+     * {String} A KML string.
+     */
+    write: function(features) {
+        if(!(features instanceof Array)) {
+            features = [features];
+        }
+        var kml = this.createElementNS(this.kmlns, "kml");
+        var folder = this.createFolderXML();
+        for(var i=0, len=features.length; i<len; ++i) {
+            folder.appendChild(this.createPlacemarkXML(features[i]));
+        }
+        kml.appendChild(folder);
+        return OpenLayers.Format.XML.prototype.write.apply(this, [kml]);
+    },
+
+    /**
+     * Method: createFolderXML
+     * Creates and returns a KML folder node
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    createFolderXML: function() {
+        // Folder name
+        var folderName = this.createElementNS(this.kmlns, "name");
+        var folderNameText = this.createTextNode(this.foldersName); 
+        folderName.appendChild(folderNameText);
+
+        // Folder description
+        var folderDesc = this.createElementNS(this.kmlns, "description");        
+        var folderDescText = this.createTextNode(this.foldersDesc); 
+        folderDesc.appendChild(folderDescText);
+
+        // Folder
+        var folder = this.createElementNS(this.kmlns, "Folder");
+        folder.appendChild(folderName);
+        folder.appendChild(folderDesc);
+        
+        return folder;
+    },
+
+    /**
+     * Method: createPlacemarkXML
+     * Creates and returns a KML placemark node representing the given feature. 
+     * 
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    createPlacemarkXML: function(feature) {        
+        // Placemark name
+        var placemarkName = this.createElementNS(this.kmlns, "name");
+        var name = (feature.attributes.name) ?
+                    feature.attributes.name : feature.id;
+        placemarkName.appendChild(this.createTextNode(name));
+
+        // Placemark description
+        var placemarkDesc = this.createElementNS(this.kmlns, "description");
+        var desc = (feature.attributes.description) ?
+                    feature.attributes.description : this.placemarksDesc;
+        placemarkDesc.appendChild(this.createTextNode(desc));
+        
+        // Placemark
+        var placemarkNode = this.createElementNS(this.kmlns, "Placemark");
+        if(feature.fid != null) {
+            placemarkNode.setAttribute("id", feature.fid);
+        }
+        placemarkNode.appendChild(placemarkName);
+        placemarkNode.appendChild(placemarkDesc);
+
+        // Geometry node (Point, LineString, etc. nodes)
+        var geometryNode = this.buildGeometryNode(feature.geometry);
+        placemarkNode.appendChild(geometryNode);        
+        
+        // TBD - deal with remaining (non name/description) attributes.
+        return placemarkNode;
+    },    
+
+    /**
+     * Method: buildGeometryNode
+     * Builds and returns a KML geometry node with the given geometry.
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Returns:
+     * {DOMElement}
+     */
+    buildGeometryNode: function(geometry) {
+        if (this.internalProjection && this.externalProjection) {
+            geometry = geometry.clone();
+            geometry.transform(this.internalProjection, 
+                               this.externalProjection);
+        }                       
+        var className = geometry.CLASS_NAME;
+        var type = className.substring(className.lastIndexOf(".") + 1);
+        var builder = this.buildGeometry[type.toLowerCase()];
+        var node = null;
+        if(builder) {
+            node = builder.apply(this, [geometry]);
+        }
+        return node;
+    },
+
+    /**
+     * Property: buildGeometry
+     * Object containing methods to do the actual geometry node building
+     *     based on geometry type.
+     */
+    buildGeometry: {
+        // TBD: Anybody care about namespace aliases here (these nodes have
+        //    no prefixes)?
+
+        /**
+         * Method: buildGeometry.point
+         * Given an OpenLayers point geometry, create a KML point.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A point geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML point node.
+         */
+        point: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "Point");
+            kml.appendChild(this.buildCoordinatesNode(geometry));
+            return kml;
+        },
+        
+        /**
+         * Method: buildGeometry.multipoint
+         * Given an OpenLayers multipoint geometry, create a KML
+         *     GeometryCollection.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A multipoint geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML GeometryCollection node.
+         */
+        multipoint: function(geometry) {
+            return this.buildGeometry.collection.apply(this, [geometry]);
+        },
+
+        /**
+         * Method: buildGeometry.linestring
+         * Given an OpenLayers linestring geometry, create a KML linestring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.LineString>} A linestring geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML linestring node.
+         */
+        linestring: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "LineString");
+            kml.appendChild(this.buildCoordinatesNode(geometry));
+            return kml;
+        },
+        
+        /**
+         * Method: buildGeometry.multilinestring
+         * Given an OpenLayers multilinestring geometry, create a KML
+         *     GeometryCollection.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A multilinestring geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML GeometryCollection node.
+         */
+        multilinestring: function(geometry) {
+            return this.buildGeometry.collection.apply(this, [geometry]);
+        },
+
+        /**
+         * Method: buildGeometry.linearring
+         * Given an OpenLayers linearring geometry, create a KML linearring.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.LinearRing>} A linearring geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML linearring node.
+         */
+        linearring: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "LinearRing");
+            kml.appendChild(this.buildCoordinatesNode(geometry));
+            return kml;
+        },
+        
+        /**
+         * Method: buildGeometry.polygon
+         * Given an OpenLayers polygon geometry, create a KML polygon.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Polygon>} A polygon geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML polygon node.
+         */
+        polygon: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "Polygon");
+            var rings = geometry.components;
+            var ringMember, ringGeom, type;
+            for(var i=0, len=rings.length; i<len; ++i) {
+                type = (i==0) ? "outerBoundaryIs" : "innerBoundaryIs";
+                ringMember = this.createElementNS(this.kmlns, type);
+                ringGeom = this.buildGeometry.linearring.apply(this,
+                                                               [rings[i]]);
+                ringMember.appendChild(ringGeom);
+                kml.appendChild(ringMember);
+            }
+            return kml;
+        },
+        
+        /**
+         * Method: buildGeometry.multipolygon
+         * Given an OpenLayers multipolygon geometry, create a KML
+         *     GeometryCollection.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Point>} A multipolygon geometry.
+         *
+         * Returns:
+         * {DOMElement} A KML GeometryCollection node.
+         */
+        multipolygon: function(geometry) {
+            return this.buildGeometry.collection.apply(this, [geometry]);
+        },
+
+        /**
+         * Method: buildGeometry.collection
+         * Given an OpenLayers geometry collection, create a KML MultiGeometry.
+         *
+         * Parameters:
+         * geometry - {<OpenLayers.Geometry.Collection>} A geometry collection.
+         *
+         * Returns:
+         * {DOMElement} A KML MultiGeometry node.
+         */
+        collection: function(geometry) {
+            var kml = this.createElementNS(this.kmlns, "MultiGeometry");
+            var child;
+            for(var i=0, len=geometry.components.length; i<len; ++i) {
+                child = this.buildGeometryNode.apply(this,
+                                                     [geometry.components[i]]);
+                if(child) {
+                    kml.appendChild(child);
+                }
+            }
+            return kml;
+        }
+    },
+
+    /**
+     * Method: buildCoordinatesNode
+     * Builds and returns the KML coordinates node with the given geometry
+     * <coordinates>...</coordinates>
+     * 
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>}
+     * 
+     * Return:
+     * {DOMElement}
+     */     
+    buildCoordinatesNode: function(geometry) {
+        var coordinatesNode = this.createElementNS(this.kmlns, "coordinates");
+        
+        var path;
+        var points = geometry.components;
+        if(points) {
+            // LineString or LinearRing
+            var point;
+            var numPoints = points.length;
+            var parts = new Array(numPoints);
+            for(var i=0; i<numPoints; ++i) {
+                point = points[i];
+                parts[i] = point.x + "," + point.y;
+            }
+            path = parts.join(" ");
+        } else {
+            // Point
+            path = geometry.x + "," + geometry.y;
+        }
+        
+        var txtNode = this.createTextNode(path);
+        coordinatesNode.appendChild(txtNode);
+        
+        return coordinatesNode;
+    },    
+
+    CLASS_NAME: "OpenLayers.Format.KML" 
+});
+/* ======================================================================
+    OpenLayers/Format/OSM.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2007 MetaCarta, Inc., published under a modified BSD license.
+ * See http://svn.openlayers.org/trunk/openlayers/repository-license.txt 
+ * for the full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/XML.js
+ * @requires OpenLayers/Feature/Vector.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**  
+ * Class: OpenLayers.Format.OSM
+ * OSM parser. Create a new instance with the 
+ *     <OpenLayers.Format.OSM> constructor.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Format.XML>
+ */
+OpenLayers.Format.OSM = OpenLayers.Class(OpenLayers.Format.XML, {
+    
+    /**
+     * APIProperty: checkTags
+     * {Boolean} Should tags be checked to determine whether something
+     * should be treated as a seperate node. Will slow down parsing.
+     * Default is false.
+     */
+    checkTags: false,
+
+    /**
+     * Property: interestingTagsExclude
+     * {Array} List of tags to exclude from 'interesting' checks on nodes.
+     * Must be set when creating the format. Will only be used if checkTags
+     * is set.
+     */
+    interestingTagsExclude: null, 
+    
+    /**
+     * APIProperty: areaTags
+     * {Array} List of tags indicating that something is an area.  
+     * Must be set when creating the format. Will only be used if 
+     * checkTags is true.
+     */
+    areaTags: null, 
+    
+    /**
+     * Constructor: OpenLayers.Format.OSM
+     * Create a new parser for OSM.
+     *
+     * Parameters:
+     * options - {Object} An optional object whose properties will be set on
+     *     this instance.
+     */
+    initialize: function(options) {
+        var layer_defaults = {
+          'interestingTagsExclude': ['source', 'source_ref', 
+              'source:ref', 'history', 'attribution', 'created_by'],
+          'areaTags': ['area', 'building', 'leisure', 'tourism', 'ruins',
+              'historic', 'landuse', 'military', 'natural', 'sport'] 
+        };
+          
+        layer_defaults = OpenLayers.Util.extend(layer_defaults, options);
+        
+        var interesting = {};
+        for (var i = 0; i < layer_defaults.interestingTagsExclude.length; i++) {
+            interesting[layer_defaults.interestingTagsExclude[i]] = true;
+        }
+        layer_defaults.interestingTagsExclude = interesting;
+        
+        var area = {};
+        for (var i = 0; i < layer_defaults.areaTags.length; i++) {
+            area[layer_defaults.areaTags[i]] = true;
+        }
+        layer_defaults.areaTags = area;
+        
+        OpenLayers.Format.XML.prototype.initialize.apply(this, [layer_defaults]);
+    },
+    
+    /**
+     * APIMethod: read
+     * Return a list of features from a OSM doc
+     
+     * Parameters:
+     * data - {Element} 
+     *
+     * Returns:
+     * An Array of <OpenLayers.Feature.Vector>s
+     */
+    read: function(doc) {
+        if (typeof doc == "string") { 
+            doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
+        }
+
+        var nodes = this.getNodes(doc);
+        var ways = this.getWays(doc);
+        
+        // Geoms will contain at least ways.length entries.
+        var feat_list = new Array(ways.length);
+        
+        for (var i = 0; i < ways.length; i++) {
+            // We know the minimal of this one ahead of time. (Could be -1
+            // due to areas/polygons)
+            var point_list = new Array(ways[i].nodes.length);
+            
+            var poly = this.isWayArea(ways[i]) ? 1 : 0; 
+            for (var j = 0; j < ways[i].nodes.length; j++) {
+               var node = nodes[ways[i].nodes[j]];
+               
+               var point = new OpenLayers.Geometry.Point(node.lon, node.lat);
+               
+               // Since OSM is topological, we stash the node ID internally. 
+               point.osm_id = parseInt(ways[i].nodes[j]);
+               point_list[j] = point;
+               
+               // We don't display nodes if they're used inside other 
+               // elements.
+               node.used = true; 
+            }
+            var geometry = null;
+            if (poly) { 
+                geometry = new OpenLayers.Geometry.Polygon(
+                    new OpenLayers.Geometry.LinearRing(point_list));
+            } else {    
+                geometry = new OpenLayers.Geometry.LineString(point_list);
+            }
+            if (this.internalProjection && this.externalProjection) {
+                geometry.transform(this.externalProjection, 
+                    this.internalProjection);
+            }        
+            var feat = new OpenLayers.Feature.Vector(geometry,
+                ways[i].tags);
+            feat.osm_id = parseInt(ways[i].id);
+            feat.fid = "way." + feat.osm_id;
+            feat_list[i] = feat;
+        } 
+        for (var node_id in nodes) {
+            var node = nodes[node_id];
+            if (!node.used || this.checkTags) {
+                var tags = null;
+                
+                if (this.checkTags) {
+                    var result = this.getTags(node.node, true);
+                    if (node.used && !result[1]) {
+                        continue;
+                    }
+                    tags = result[0];
+                } else { 
+                    tags = this.getTags(node.node);
+                }    
+                
+                var feat = new OpenLayers.Feature.Vector(
+                    new OpenLayers.Geometry.Point(node['lon'], node['lat']),
+                    tags);
+                if (this.internalProjection && this.externalProjection) {
+                    feat.geometry.transform(this.externalProjection, 
+                        this.internalProjection);
+                }        
+                feat.osm_id = parseInt(node_id); 
+                feat.fid = "node." + feat.osm_id;
+                feat_list.push(feat);
+            }   
+            // Memory cleanup
+            node.node = null;
+        }        
+        return feat_list;
+    },
+
+    /**
+     * Method: getNodes
+     * Return the node items from a doc.  
+     *
+     * Parameters:
+     * node - {DOMElement} node to parse tags from
+     */
+    getNodes: function(doc) {
+        var node_list = doc.getElementsByTagName("node");
+        var nodes = {};
+        for (var i = 0; i < node_list.length; i++) {
+            var node = node_list[i];
+            var id = node.getAttribute("id");
+            nodes[id] = {
+                'lat': node.getAttribute("lat"),
+                'lon': node.getAttribute("lon"),
+                'node': node
+            };
+        }
+        return nodes;
+    },
+
+    /**
+     * Method: getWays
+     * Return the way items from a doc.  
+     *
+     * Parameters:
+     * node - {DOMElement} node to parse tags from
+     */
+    getWays: function(doc) {
+        var way_list = doc.getElementsByTagName("way");
+        var return_ways = [];
+        for (var i = 0; i < way_list.length; i++) {
+            var way = way_list[i];
+            var way_object = {
+              id: way.getAttribute("id")
+            };
+            
+            way_object.tags = this.getTags(way);
+            
+            var node_list = way.getElementsByTagName("nd");
+            
+            way_object.nodes = new Array(node_list.length);
+            
+            for (var j = 0; j < node_list.length; j++) {
+                way_object.nodes[j] = node_list[j].getAttribute("ref");
+            }  
+            return_ways.push(way_object);
+        }
+        return return_ways; 
+        
+    },  
+    
+    /**
+     * Method: getTags
+     * Return the tags list attached to a specific DOM element.
+     *
+     * Parameters:
+     * node - {DOMElement} node to parse tags from
+     * interesting_tags - {Boolean} whether the return from this function should
+     *    return a boolean indicating that it has 'interesting tags' -- 
+     *    tags like attribution and source are ignored. (To change the list
+     *    of tags, see interestingTagsExclude)
+     * 
+     * Returns:
+     * tags - {Object} hash of tags
+     * interesting - {Boolean} if interesting_tags is passed, returns
+     *     whether there are any interesting tags on this element.
+     */
+    getTags: function(dom_node, interesting_tags) {
+        var tag_list = dom_node.getElementsByTagName("tag");
+        var tags = {};
+        var interesting = false;
+        for (var j = 0; j < tag_list.length; j++) {
+            var key = tag_list[j].getAttribute("k");
+            tags[key] = tag_list[j].getAttribute("v");
+            if (interesting_tags) {
+                if (!this.interestingTagsExclude[key]) {
+                    interesting = true;
+                }
+            }    
+        }  
+        return interesting_tags ? [tags, interesting] : tags;     
+    },
+
+    /** 
+     * Method: isWayArea
+     * Given a way object from getWays, check whether the tags and geometry
+     * indicate something is an area.
+     *
+     * Returns:
+     * {Boolean}
+     */
+    isWayArea: function(way) { 
+        var poly_shaped = false;
+        var poly_tags = false;
+        
+        if (way.nodes[0] == way.nodes[way.nodes.length - 1]) {
+            poly_shaped = true;
+        }
+        if (this.checkTags) {
+            for(var key in way.tags) {
+                if (this.areaTags[key]) {
+                    poly_tags = true;
+                    break;
+                }
+            }
+        }    
+        return poly_shaped && (this.checkTags ? poly_tags : true);            
+    }, 
+
+    /**
+     * APIMethod: write 
+     * Takes a list of features, returns a serialized OSM format file for use
+     * in tools like JOSM.
+     *
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)}
+     */
+    write: function(features) { 
+        if (!(features instanceof Array)) {
+            features = [features];
+        }
+        
+        this.osm_id = 1;
+        this.created_nodes = {};
+        var root_node = this.createElementNS(null, "osm");
+        root_node.setAttribute("version", "0.5");
+        root_node.setAttribute("generator", "OpenLayers "+ OpenLayers.VERSION_NUMBER);
+
+        // Loop backwards, because the deserializer puts nodes last, and 
+        // we want them first if possible
+        for(var i = features.length - 1; i >= 0; i--) {
+            var nodes = this.createFeatureNodes(features[i]);
+            for (var j = 0; j < nodes.length; j++) {
+                root_node.appendChild(nodes[j]);
+            }    
+        }
+        return OpenLayers.Format.XML.prototype.write.apply(this, [root_node]);
+    },
+
+    /**
+     * Method: createFeatureNodes
+     * Takes a feature, returns a list of nodes from size 0->n.
+     * Will include all pieces of the serialization that are required which
+     * have not already been created. Calls out to createXML based on geometry
+     * type.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     */
+    createFeatureNodes: function(feature) {
+        var nodes = [];
+        var className = feature.geometry.CLASS_NAME;
+        var type = className.substring(className.lastIndexOf(".") + 1);
+        type = type.toLowerCase();
+        var builder = this.createXML[type];
+        if (builder) {
+            nodes = builder.apply(this, [feature]);
+        }
+        return nodes;
+    },
+    
+    /**
+     * Method: createXML
+     * Takes a feature, returns a list of nodes from size 0->n.
+     * Will include all pieces of the serialization that are required which
+     * have not already been created.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     */
+    createXML: {
+        'point': function(point) {
+            var id = null;
+            var geometry = point.geometry ? point.geometry : point;
+            var already_exists = false; // We don't return anything if the node
+                                        // has already been created
+            if (point.osm_id) {
+                id = point.osm_id;
+                if (this.created_nodes[id]) {
+                    already_exists = true;
+                }    
+            } else {
+               id = -this.osm_id;
+               this.osm_id++; 
+            }
+            if (already_exists) {
+                node = this.created_nodes[id];
+            } else {    
+                var node = this.createElementNS(null, "node");
+            }
+            this.created_nodes[id] = node;
+            node.setAttribute("id", id);
+            node.setAttribute("lon", geometry.x); 
+            node.setAttribute("lat", geometry.y);
+            if (point.attributes) {
+                this.serializeTags(point, node);
+            }
+            this.setState(point, node);
+            return already_exists ? [] : [node];
+        }, 
+        linestring: function(feature) {
+            var nodes = [];
+            var geometry = feature.geometry;
+            if (feature.osm_id) {
+                id = feature.osm_id;
+            } else {
+               id = -this.osm_id;
+               this.osm_id++; 
+            }
+            var way = this.createElementNS(null, "way");
+            way.setAttribute("id", id);
+            for (var i = 0; i < geometry.components.length; i++) {
+                var node = this.createXML['point'].apply(this, [geometry.components[i]]);
+                if (node.length) {
+                    node = node[0];
+                    var node_ref = node.getAttribute("id");
+                    nodes.push(node);
+                } else {
+                    node_ref = geometry.components[i].osm_id;
+                    node = this.created_nodes[node_ref];
+                }
+                this.setState(feature, node);
+                var nd_dom = this.createElementNS(null, "nd");
+                nd_dom.setAttribute("ref", node_ref);
+                way.appendChild(nd_dom);
+            }
+            this.serializeTags(feature, way);
+            nodes.push(way);
+            
+            return nodes;
+        },
+        polygon: function(feature) {
+            var attrs = OpenLayers.Util.extend({'area':'yes'}, feature.attributes);
+            var feat = new OpenLayers.Feature.Vector(feature.geometry.components[0], attrs); 
+            feat.osm_id = feature.osm_id;
+            return this.createXML['linestring'].apply(this, [feat]);
+        }
+    },
+
+    /**
+     * Method: serializeTags
+     * Given a feature, serialize the attributes onto the given node.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     * node - {DOMNode}
+     */
+    serializeTags: function(feature, node) {
+        for (var key in feature.attributes) {
+            var tag = this.createElementNS(null, "tag");
+            tag.setAttribute("k", key);
+            tag.setAttribute("v", feature.attributes[key]);
+            node.appendChild(tag);
+        }
+    },
+
+    /**
+     * Method: setState 
+     * OpenStreetMap has a convention that 'state' is stored for modification or deletion.
+     * This allows the file to be uploaded via JOSM or the bulk uploader tool.
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>}
+     * node - {DOMNode}
+     */
+    setState: function(feature, node) {
+        if (feature.state) {
+            var state = null;
+            switch(feature.state) {
+                case OpenLayers.State.UPDATE:
+                    state = "modify";
+                case OpenLayers.State.DELETE:
+                    state = "delete";
+            }
+            if (state) {
+                node.setAttribute("action", state);
+            }
+        }    
+    },
+
+    CLASS_NAME: "OpenLayers.Format.OSM" 
+});     
+/* ======================================================================
+    OpenLayers/Geometry/LinearRing.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Geometry.LinearRing
+ * 
+ * A Linear Ring is a special LineString which is closed. It closes itself 
+ * automatically on every addPoint/removePoint by adding a copy of the first
+ * point as the last point. 
+ * 
+ * Also, as it is the first in the line family to close itself, a getArea()
+ * function is defined to calculate the enclosed area of the linearRing
+ * 
+ * Inherits:
+ *  - <OpenLayers.Geometry.LineString>
+ */
+OpenLayers.Geometry.LinearRing = OpenLayers.Class(
+  OpenLayers.Geometry.LineString, {
+
+    /**
+     * Property: componentTypes
+     * {Array(String)} An array of class names representing the types of 
+     *                 components that the collection can include.  A null 
+     *                 value means the component types are not restricted.
+     */
+    componentTypes: ["OpenLayers.Geometry.Point"],
+
+    /**
+     * Constructor: OpenLayers.Geometry.LinearRing
+     * Linear rings are constructed with an array of points.  This array
+     *     can represent a closed or open ring.  If the ring is open (the last
+     *     point does not equal the first point), the constructor will close
+     *     the ring.  If the ring is already closed (the last point does equal
+     *     the first point), it will be left closed.
+     * 
+     * Parameters:
+     * points - {Array(<OpenLayers.Geometry.Point>)} points
+     */
+    initialize: function(points) {
+        OpenLayers.Geometry.LineString.prototype.initialize.apply(this, 
+                                                                  arguments);
+    },
+
+    /**
+     * APIMethod: addComponent
+     * Adds a point to geometry components.  If the point is to be added to
+     *     the end of the components array and it is the same as the last point
+     *     already in that array, the duplicate point is not added.  This has 
+     *     the effect of closing the ring if it is not already closed, and 
+     *     doing the right thing if it is already closed.  This behavior can 
+     *     be overridden by calling the method with a non-null index as the 
+     *     second argument.
+     *
+     * Parameter:
+     * point - {<OpenLayers.Geometry.Point>}
+     * index - {Integer} Index into the array to insert the component
+     * 
+     * Returns:
+     * {Boolean} Was the Point successfully added?
+     */
+    addComponent: function(point, index) {
+        var added = false;
+
+        //remove last point
+        var lastPoint = this.components.pop();
+
+        // given an index, add the point
+        // without an index only add non-duplicate points
+        if(index != null || !point.equals(lastPoint)) {
+            added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
+                                                                    arguments);
+        }
+
+        //append copy of first point
+        var firstPoint = this.components[0];
+        OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
+                                                                [firstPoint]);
+        
+        return added;
+    },
+    
+    /**
+     * APIMethod: removeComponent
+     * Removes a point from geometry components.
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>}
+     */
+    removeComponent: function(point) {
+        if (this.components.length > 4) {
+
+            //remove last point
+            this.components.pop();
+            
+            //remove our point
+            OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, 
+                                                                    arguments);
+            //append copy of first point
+            var firstPoint = this.components[0];
+            OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
+                                                                [firstPoint]);
+        }
+    },
+    
+    /**
+     * APIMethod: move
+     * Moves a collection in place
+     *
+     * Parameters:
+     * x - {Float} The x-displacement (in map units)
+     * y - {Float} The y-displacement (in map units)
+     */
+    move: function(x, y) {
+        for(var i = 0, len=this.components.length; i<len - 1; i++) {
+            this.components[i].move(x, y);
+        }
+    },
+
+    /**
+     * APIMethod: rotate
+     * Rotate a geometry around some origin
+     *
+     * Parameters:
+     * angle - {Float} Rotation angle in degrees (measured counterclockwise
+     *                 from the positive x-axis)
+     * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
+     */
+    rotate: function(angle, origin) {
+        for(var i=0, len=this.components.length; i<len - 1; ++i) {
+            this.components[i].rotate(angle, origin);
+        }
+    },
+
+    /**
+     * APIMethod: resize
+     * Resize a geometry relative to some origin.  Use this method to apply
+     *     a uniform scaling to a geometry.
+     *
+     * Parameters:
+     * scale - {Float} Factor by which to scale the geometry.  A scale of 2
+     *                 doubles the size of the geometry in each dimension
+     *                 (lines, for example, will be twice as long, and polygons
+     *                 will have four times the area).
+     * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
+     * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
+     */
+    resize: function(scale, origin, ratio) {
+        for(var i=0, len=this.components.length; i<len - 1; ++i) {
+            this.components[i].resize(scale, origin, ratio);
+        }
+    },
+    
+    /**
+     * APIMethod: transform
+     * Reproject the components geometry from source to dest.
+     *
+     * Parameters:
+     * source - {<OpenLayers.Projection>}
+     * dest - {<OpenLayers.Projection>}
+     * 
+     * Returns:
+     * {<OpenLayers.Geometry>} 
+     */
+    transform: function(source, dest) {
+        if (source && dest) {
+            for (var i=0, len=this.components.length; i<len - 1; i++) {
+                var component = this.components[i];
+                component.transform(source, dest);
+            }
+            this.bounds = null;
+        }
+        return this;
+    },
+
+    /**
+     * APIMethod: getArea
+     * Note - The area is positive if the ring is oriented CW, otherwise
+     *         it will be negative.
+     * 
+     * Returns:
+     * {Float} The signed area for a ring.
+     */
+    getArea: function() {
+        var area = 0.0;
+        if ( this.components && (this.components.length > 2)) {
+            var sum = 0.0;
+            for (var i=0, len=this.components.length; i<len - 1; i++) {
+                var b = this.components[i];
+                var c = this.components[i+1];
+                sum += (b.x + c.x) * (c.y - b.y);
+            }
+            area = - sum / 2.0;
+        }
+        return area;
+    },
+    
+    /**
+     * Method: containsPoint
+     * Test if a point is inside a linear ring.  For the case where a point
+     *     is coincident with a linear ring edge, returns 1.  Otherwise,
+     *     returns boolean.
+     *
+     * Parameters:
+     * point - {<OpenLayers.Geometry.Point>}
+     *
+     * Returns:
+     * {Boolean | Number} The point is inside the linear ring.  Returns 1 if
+     *     the point is coincident with an edge.  Returns boolean otherwise.
+     */
+    containsPoint: function(point) {
+        var approx = OpenLayers.Number.limitSigDigs;
+        var digs = 14;
+        var px = approx(point.x, digs);
+        var py = approx(point.y, digs);
+        function getX(y, x1, y1, x2, y2) {
+            return (((x1 - x2) * y) + ((x2 * y1) - (x1 * y2))) / (y1 - y2);
+        }
+        var numSeg = this.components.length - 1;
+        var start, end, x1, y1, x2, y2, cx, cy;
+        var crosses = 0;
+        for(var i=0; i<numSeg; ++i) {
+            start = this.components[i];
+            x1 = approx(start.x, digs);
+            y1 = approx(start.y, digs);
+            end = this.components[i + 1];
+            x2 = approx(end.x, digs);
+            y2 = approx(end.y, digs);
+            
+            /**
+             * The following conditions enforce five edge-crossing rules:
+             *    1. points coincident with edges are considered contained;
+             *    2. an upward edge includes its starting endpoint, and
+             *    excludes its final endpoint;
+             *    3. a downward edge excludes its starting endpoint, and
+             *    includes its final endpoint;
+             *    4. horizontal edges are excluded; and
+             *    5. the edge-ray intersection point must be strictly right
+             *    of the point P.
+             */
+            if(y1 == y2) {
+                // horizontal edge
+                if(py == y1) {
+                    // point on horizontal line
+                    if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
+                       x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
+                        // point on edge
+                        crosses = -1;
+                        break;
+                    }
+                }
+                // ignore other horizontal edges
+                continue;
+            }
+            cx = approx(getX(py, x1, y1, x2, y2), digs);
+            if(cx == px) {
+                // point on line
+                if(y1 < y2 && (py >= y1 && py <= y2) || // upward
+                   y1 > y2 && (py <= y1 && py >= y2)) { // downward
+                    // point on edge
+                    crosses = -1;
+                    break;
+                }
+            }
+            if(cx <= px) {
+                // no crossing to the right
+                continue;
+            }
+            if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
+                // no crossing
+                continue;
+            }
+            if(y1 < y2 && (py >= y1 && py < y2) || // upward
+               y1 > y2 && (py < y1 && py >= y2)) { // downward
+                ++crosses;
+            }
+        }
+        var contained = (crosses == -1) ?
+            // on edge
+            1 :
+            // even (out) or odd (in)
+            !!(crosses & 1);
+
+        return contained;
+    },
+
+    /**
+     * APIMethod: intersects
+     * Determine if the input geometry intersects this one.
+     *
+     * Parameters:
+     * geometry - {<OpenLayers.Geometry>} Any type of geometry.
+     *
+     * Returns:
+     * {Boolean} The input geometry intersects this one.
+     */
+    intersects: function(geometry) {
+        var intersect = false;
+        if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
+            intersect = this.containsPoint(geometry);
+        } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
+            intersect = geometry.intersects(this);
+        } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
+            intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply(
+                this, [geometry]
+            );
+        } else {
+            // check for component intersections
+            for(var i=0, len=geometry.components.length; i<len; ++ i) {
+                intersect = geometry.components[i].intersects(this);
+                if(intersect) {
+                    break;
+                }
+            }
+        }
+        return intersect;
+    },
+
+    CLASS_NAME: "OpenLayers.Geometry.LinearRing"
+});
+/* ======================================================================
+    OpenLayers/Handler/Path.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Point.js
+ * @requires OpenLayers/Geometry/Point.js
+ * @requires OpenLayers/Geometry/LineString.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Path
+ * Handler to draw a path on the map.  Path is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Handler.Point>
+ */
+OpenLayers.Handler.Path = OpenLayers.Class(OpenLayers.Handler.Point, {
+    
+    /**
+     * Property: line
+     * {<OpenLayers.Feature.Vector>}
+     */
+    line: null,
+    
+    /**
+     * Property: freehand
+     * {Boolean} In freehand mode, the handler starts the path on mouse down,
+     * adds a point for every mouse move, and finishes the path on mouse up.
+     * Outside of freehand mode, a point is added to the path on every mouse
+     * click and double-click finishes the path.
+     */
+    freehand: false,
+    
+    /**
+     * Property: freehandToggle
+     * {String} If set, freehandToggle is checked on mouse events and will set
+     * the freehand mode to the opposite of this.freehand.  To disallow
+     * toggling between freehand and non-freehand mode, set freehandToggle to
+     * null.  Acceptable toggle values are 'shiftKey', 'ctrlKey', and 'altKey'.
+     */
+    freehandToggle: 'shiftKey',
+
+    /**
+     * Constructor: OpenLayers.Handler.Path
+     * Create a new path hander
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} 
+     * callbacks - {Object} An object with a 'done' property whos value is a
+     *     function to be called when the path drawing is finished. The 
+     *     callback should expect to recieve a single argument, the line 
+     *     string geometry. If the callbacks object contains a 'point' 
+     *     property, this function will be sent each point as they are added.  
+     *     If the callbacks object contains a 'cancel' property, this function 
+     *     will be called when the handler is deactivated while drawing. The 
+     *     cancel should expect to receive a geometry.
+     * options - {Object} An optional object with properties to be set on the
+     *           handler
+     */
+    initialize: function(control, callbacks, options) {
+        OpenLayers.Handler.Point.prototype.initialize.apply(this, arguments);
+    },
+        
+    /**
+     * Method: createFeature
+     * Add temporary geometries
+     */
+    createFeature: function() {
+        this.line = new OpenLayers.Feature.Vector(
+                                        new OpenLayers.Geometry.LineString());
+        this.point = new OpenLayers.Feature.Vector(
+                                        new OpenLayers.Geometry.Point());
+    },
+        
+    /**
+     * Method: destroyFeature
+     * Destroy temporary geometries
+     */
+    destroyFeature: function() {
+        OpenLayers.Handler.Point.prototype.destroyFeature.apply(this);
+        if(this.line) {
+            this.line.destroy();
+        }
+        this.line = null;
+    },
+    
+    /**
+     * Method: addPoint
+     * Add point to geometry.  Send the point index to override
+     * the behavior of LinearRing that disregards adding duplicate points.
+     */
+    addPoint: function() {
+        this.line.geometry.addComponent(this.point.geometry.clone(),
+                                        this.line.geometry.components.length);
+        this.callback("point", [this.point.geometry]);
+    },
+    
+    /**
+     * Method: freehandMode
+     * Determine whether to behave in freehand mode or not.
+     *
+     * Returns:
+     * {Boolean}
+     */
+    freehandMode: function(evt) {
+        return (this.freehandToggle && evt[this.freehandToggle]) ?
+                    !this.freehand : this.freehand;
+    },
+
+    /**
+     * Method: modifyFeature
+     * Modify the existing geometry given the new point
+     */
+    modifyFeature: function() {
+        var index = this.line.geometry.components.length - 1;
+        this.line.geometry.components[index].x = this.point.geometry.x;
+        this.line.geometry.components[index].y = this.point.geometry.y;
+        this.line.geometry.components[index].clearBounds();
+    },
+    
+    /**
+     * Method: drawFeature
+     * Render geometries on the temporary layer.
+     */
+    drawFeature: function() {
+        this.layer.drawFeature(this.line, this.style);
+        this.layer.drawFeature(this.point, this.style);
+    },
+
+    /**
+     * Method: geometryClone
+     * Return a clone of the relevant geometry.
+     *
+     * Returns:
+     * {<OpenLayers.Geometry.LineString>}
+     */
+    geometryClone: function() {
+        return this.line.geometry.clone();
+    },
+
+    /**
+     * Method: mousedown
+     * Handle mouse down.  Add a new point to the geometry and
+     * render it. Return determines whether to propagate the event on the map.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mousedown: function(evt) {
+        // ignore double-clicks
+        if (this.lastDown && this.lastDown.equals(evt.xy)) {
+            return false;
+        }
+        if(this.lastDown == null) {
+            this.createFeature();
+        }
+        this.mouseDown = true;
+        this.lastDown = evt.xy;
+        var lonlat = this.control.map.getLonLatFromPixel(evt.xy);
+        this.point.geometry.x = lonlat.lon;
+        this.point.geometry.y = lonlat.lat;
+        if((this.lastUp == null) || !this.lastUp.equals(evt.xy)) {
+            this.addPoint();
+        }
+        this.drawFeature();
+        this.drawing = true;
+        return false;
+    },
+
+    /**
+     * Method: mousemove
+     * Handle mouse move.  Adjust the geometry and redraw.
+     * Return determines whether to propagate the event on the map.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mousemove: function (evt) {
+        if(this.drawing) { 
+            var lonlat = this.map.getLonLatFromPixel(evt.xy);
+            this.point.geometry.x = lonlat.lon;
+            this.point.geometry.y = lonlat.lat;
+            this.point.geometry.clearBounds();
+            if(this.mouseDown && this.freehandMode(evt)) {
+                this.addPoint();
+            } else {
+                this.modifyFeature();
+            }
+            this.drawFeature();
+        }
+        return true;
+    },
+    
+    /**
+     * Method: mouseup
+     * Handle mouse up.  Send the latest point in the geometry to
+     * the control. Return determines whether to propagate the event on the map.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    mouseup: function (evt) {
+        this.mouseDown = false;
+        if(this.drawing) {
+            if(this.freehandMode(evt)) {
+                this.finalize();
+            } else {
+                if(this.lastUp == null) {
+                   this.addPoint();
+                }
+                this.lastUp = evt.xy;
+            }
+            return false;
+        }
+        return true;
+    },
+  
+    /**
+     * Method: dblclick 
+     * Handle double-clicks.  Finish the geometry and send it back
+     * to the control.
+     * 
+     * Parameters:
+     * evt - {Event} The browser event
+     *
+     * Returns: 
+     * {Boolean} Allow event propagation
+     */
+    dblclick: function(evt) {
+        if(!this.freehandMode(evt)) {
+            var index = this.line.geometry.components.length - 1;
+            this.line.geometry.removeComponent(this.line.geometry.components[index]);
+            this.finalize();
+        }
+        return false;
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Path"
+});
+/* ======================================================================
+    OpenLayers/Format/WFS.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Format/GML.js
+ */
+
+/**
+ * Class: OpenLayers.Format.WFS
+ * Read/Write WFS. 
+ */
+OpenLayers.Format.WFS = OpenLayers.Class(OpenLayers.Format.GML, {
+    
+    /** 
+     * Property: layer
+     */
+    layer: null,
+    
+    /**
+     * APIProperty: wfsns
+     */
+    wfsns: "http://www.opengis.net/wfs",
+    
+    /**
+     * Property: ogcns
+     */
+    ogcns: "http://www.opengis.net/ogc",
+    
+    /*
+     * Constructor: OpenLayers.Format.WFS
+     * Create a WFS-T formatter. This requires a layer: that layer should
+     * have two properties: geometry_column and typename. The parser
+     * for this format is subclassed entirely from GML: There is a writer 
+     * only, which uses most of the code from the GML layer, and wraps
+     * it in transactional elements.
+     * 
+     * Parameters: 
+     * options - {Object} 
+     * layer - {<OpenLayers.Layer>} 
+     */
+    
+    initialize: function(options, layer) {
+        OpenLayers.Format.GML.prototype.initialize.apply(this, [options]);
+        this.layer = layer;
+        if (this.layer.featureNS) {
+            this.featureNS = this.layer.featureNS;
+        }    
+        if (this.layer.options.geometry_column) {
+            this.geometryName = this.layer.options.geometry_column;
+        }
+        if (this.layer.options.typename) {
+            this.featureName = this.layer.options.typename;
+        }
+    },
+    
+    /**
+     * Method: write 
+     * Takes a feature list, and generates a WFS-T Transaction 
+     *
+     * Parameters:
+     * features - {Array(<OpenLayers.Feature.Vector>)} 
+     */
+    write: function(features) {
+    
+        var transaction = this.createElementNS(this.wfsns, 'wfs:Transaction');
+        transaction.setAttribute("version","1.0.0");
+        transaction.setAttribute("service","WFS");
+        for (var i=0; i < features.length; i++) {
+            switch (features[i].state) {
+                case OpenLayers.State.INSERT:
+                    transaction.appendChild(this.insert(features[i]));
+                    break;
+                case OpenLayers.State.UPDATE:
+                    transaction.appendChild(this.update(features[i]));
+                    break;
+                case OpenLayers.State.DELETE:
+                    transaction.appendChild(this.remove(features[i]));
+                    break;
+            }
+        }
+        
+        return OpenLayers.Format.XML.prototype.write.apply(this,[transaction]);
+    },
+   
+    /**
+     * Method: createFeatureXML
+     *
+     * Parameters: 
+     * feature - {<OpenLayers.Feature.Vector>}
+     */ 
+    createFeatureXML: function(feature) {
+        var geometryNode = this.buildGeometryNode(feature.geometry);
+        var geomContainer = this.createElementNS(this.featureNS, "feature:" + this.geometryName);
+        geomContainer.appendChild(geometryNode);
+        var featureContainer = this.createElementNS(this.featureNS, "feature:" + this.featureName);
+        featureContainer.appendChild(geomContainer);
+        for(var attr in feature.attributes) {
+            var attrText = this.createTextNode(feature.attributes[attr]); 
+            var nodename = attr;
+            if (attr.search(":") != -1) {
+                nodename = attr.split(":")[1];
+            }    
+            var attrContainer = this.createElementNS(this.featureNS, "feature:" + nodename);
+            attrContainer.appendChild(attrText);
+            featureContainer.appendChild(attrContainer);
+        }    
+        return featureContainer;
+    },
+    
+    /**
+     * Method: insert 
+     * Takes a feature, and generates a WFS-T Transaction "Insert" 
+     *
+     * Parameters: 
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    insert: function(feature) {
+        var insertNode = this.createElementNS(this.wfsns, 'wfs:Insert');
+        insertNode.appendChild(this.createFeatureXML(feature));
+        return insertNode;
+    },
+    
+    /**
+     * Method: update
+     * Takes a feature, and generates a WFS-T Transaction "Update" 
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    update: function(feature) {
+        if (!feature.fid) { OpenLayers.Console.userError(OpenLayers.i18n("noFID")); }
+        var updateNode = this.createElementNS(this.wfsns, 'wfs:Update');
+        updateNode.setAttribute("typeName", this.layerName);
+
+        var propertyNode = this.createElementNS(this.wfsns, 'wfs:Property');
+        var nameNode = this.createElementNS(this.wfsns, 'wfs:Name');
+        
+        var txtNode = this.createTextNode(this.geometryName);
+        nameNode.appendChild(txtNode);
+        propertyNode.appendChild(nameNode);
+        
+        var valueNode = this.createElementNS(this.wfsns, 'wfs:Value');
+        
+        var geometryNode = this.buildGeometryNode(feature.geometry);
+        
+        if(feature.layer){
+            geometryNode.setAttribute(
+                "srsName", feature.layer.projection.getCode()
+            );
+        }
+        
+        valueNode.appendChild(geometryNode);
+        
+        propertyNode.appendChild(valueNode);
+        updateNode.appendChild(propertyNode);
+        
+         // add in attributes
+        for(var propName in feature.attributes) {
+            propertyNode = this.createElementNS(this.wfsns, 'wfs:Property');
+            nameNode = this.createElementNS(this.wfsns, 'wfs:Name');
+            nameNode.appendChild(this.createTextNode(propName));
+            propertyNode.appendChild(nameNode);
+            valueNode = this.createElementNS(this.wfsns, 'wfs:Value');
+            valueNode.appendChild(this.createTextNode(feature.attributes[propName]));
+            propertyNode.appendChild(valueNode);
+            updateNode.appendChild(propertyNode);
+        }
+        
+        
+        var filterNode = this.createElementNS(this.ogcns, 'ogc:Filter');
+        var filterIdNode = this.createElementNS(this.ogcns, 'ogc:FeatureId');
+        filterIdNode.setAttribute("fid", feature.fid);
+        filterNode.appendChild(filterIdNode);
+        updateNode.appendChild(filterNode);
+
+        return updateNode;
+    },
+    
+    /**
+     * Method: remove 
+     * Takes a feature, and generates a WFS-T Transaction "Delete" 
+     *
+     * Parameters:
+     * feature - {<OpenLayers.Feature.Vector>} 
+     */
+    remove: function(feature) {
+        if (!feature.fid) { 
+            OpenLayers.Console.userError(OpenLayers.i18n("noFID")); 
+            return false; 
+        }
+        var deleteNode = this.createElementNS(this.wfsns, 'wfs:Delete');
+        deleteNode.setAttribute("typeName", this.layerName);
+
+        var filterNode = this.createElementNS(this.ogcns, 'ogc:Filter');
+        var filterIdNode = this.createElementNS(this.ogcns, 'ogc:FeatureId');
+        filterIdNode.setAttribute("fid", feature.fid);
+        filterNode.appendChild(filterIdNode);
+        deleteNode.appendChild(filterNode);
+
+        return deleteNode;
+    },
+
+    /**
+     * APIMethod: destroy
+     * Remove ciruclar ref to layer 
+     */
+    destroy: function() {
+        this.layer = null;
+    },
+
+    CLASS_NAME: "OpenLayers.Format.WFS" 
+});    
+/* ======================================================================
+    OpenLayers/Handler/Polygon.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Geometry/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Handler.Polygon
+ * Handler to draw a polygon on the map.  Polygon is displayed on mouse down,
+ * moves on mouse move, and is finished on mouse up.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Handler.Path>
+ *  - <OpenLayers.Handler>
+ */
+OpenLayers.Handler.Polygon = OpenLayers.Class(OpenLayers.Handler.Path, {
+    
+    /**
+     * Parameter: polygon
+     * {<OpenLayers.Feature.Vector>}
+     */
+    polygon: null,
+
+    /**
+     * Constructor: OpenLayers.Handler.Polygon
+     * Create a Polygon Handler.
+     *
+     * Parameters:
+     * control - {<OpenLayers.Control>} 
+     * callbacks - {Object} An object with a 'done' property whos value is
+     *                          a function to be called when the path drawing is
+     *                          finished. The callback should expect to recieve a
+     *                          single argument, the polygon geometry.
+     *                          If the callbacks object contains a 'point'
+     *                          property, this function will be sent each point
+     *                          as they are added.  If the callbacks object contains
+     *                          a 'cancel' property, this function will be called when
+     *                          the handler is deactivated while drawing.  The cancel
+     *                          should expect to receive a geometry.
+     * options - {Object} 
+     */
+    initialize: function(control, callbacks, options) {
+        OpenLayers.Handler.Path.prototype.initialize.apply(this, arguments);
+    },
+    
+    /**
+     * Method: createFeature
+     * Add temporary geometries
+     */
+    createFeature: function() {
+        this.polygon = new OpenLayers.Feature.Vector(
+                                        new OpenLayers.Geometry.Polygon());
+        this.line = new OpenLayers.Feature.Vector(
+                                        new OpenLayers.Geometry.LinearRing());
+        this.polygon.geometry.addComponent(this.line.geometry);
+        this.point = new OpenLayers.Feature.Vector(
+                                        new OpenLayers.Geometry.Point());
+    },
+
+    /**
+     * Method: destroyFeature
+     * Destroy temporary geometries
+     */
+    destroyFeature: function() {
+        OpenLayers.Handler.Path.prototype.destroyFeature.apply(this);
+        if(this.polygon) {
+            this.polygon.destroy();
+        }
+        this.polygon = null;
+    },
+
+    /**
+     * Method: modifyFeature
+     * Modify the existing geometry given the new point
+     * 
+     */
+    modifyFeature: function() {
+        var index = this.line.geometry.components.length - 2;
+        this.line.geometry.components[index].x = this.point.geometry.x;
+        this.line.geometry.components[index].y = this.point.geometry.y;
+        this.line.geometry.components[index].clearBounds();
+    },
+
+    /**
+     * Method: drawFeature
+     * Render geometries on the temporary layer.
+     */
+    drawFeature: function() {
+        this.layer.drawFeature(this.polygon, this.style);
+        this.layer.drawFeature(this.point, this.style);
+    },
+
+    /**
+     * Method: geometryClone
+     * Return a clone of the relevant geometry.
+     *
+     * Returns:
+     * {<OpenLayers.Geometry.Polygon>}
+     */
+    geometryClone: function() {
+        return this.polygon.geometry.clone();
+    },
+
+    /**
+     * Method: dblclick
+     * Handle double-clicks.  Finish the geometry and send it back
+     * to the control.
+     * 
+     * Parameters:
+     * evt - {Event} 
+     */
+    dblclick: function(evt) {
+        if(!this.freehandMode(evt)) {
+            // remove the penultimate point
+            var index = this.line.geometry.components.length - 2;
+            this.line.geometry.removeComponent(this.line.geometry.components[index]);
+            this.finalize();
+        }
+        return false;
+    },
+
+    CLASS_NAME: "OpenLayers.Handler.Polygon"
+});
+/* ======================================================================
+    OpenLayers/Control/EditingToolbar.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control/Panel.js
+ * @requires OpenLayers/Control/Navigation.js
+ * @requires OpenLayers/Control/DrawFeature.js
+ * @requires OpenLayers/Handler/Point.js
+ * @requires OpenLayers/Handler/Path.js
+ * @requires OpenLayers/Handler/Polygon.js
+ */
+
+/**
+ * Class: OpenLayers.Control.EditingToolbar 
+ */
+OpenLayers.Control.EditingToolbar = OpenLayers.Class(
+  OpenLayers.Control.Panel, {
+
+    /**
+     * Constructor: OpenLayers.Control.EditingToolbar
+     * Create an editing toolbar for a given layer. 
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer.Vector>} 
+     * options - {Object} 
+     */
+    initialize: function(layer, options) {
+        OpenLayers.Control.Panel.prototype.initialize.apply(this, [options]);
+        
+        this.addControls(
+          [ new OpenLayers.Control.Navigation() ]
+        );  
+        var controls = [
+          new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Point, {'displayClass': 'olControlDrawFeaturePoint'}),
+          new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Path, {'displayClass': 'olControlDrawFeaturePath'}),
+          new OpenLayers.Control.DrawFeature(layer, OpenLayers.Handler.Polygon, {'displayClass': 'olControlDrawFeaturePolygon'})
+        ];
+        for (var i=0, len=controls.length; i<len; i++) {
+            controls[i].featureAdded = function(feature) { feature.state = OpenLayers.State.INSERT; };
+        }
+        this.addControls(controls);
+    },
+
+    /**
+     * Method: draw
+     * calls the default draw, and then activates mouse defaults.
+     *
+     * Returns:
+     * {DOMElement}
+     */
+    draw: function() {
+        var div = OpenLayers.Control.Panel.prototype.draw.apply(this, arguments);
+        this.activateControl(this.controls[0]);
+        return div;
+    },
+
+    CLASS_NAME: "OpenLayers.Control.EditingToolbar"
+});    

Modified: trunk/lib/OpenLayers/theme/default/style.css
===================================================================
--- trunk/lib/OpenLayers/theme/default/style.css	2008-09-05 14:31:06 UTC (rev 1498)
+++ trunk/lib/OpenLayers/theme/default/style.css	2008-09-05 14:39:19 UTC (rev 1499)
@@ -1,5 +1,15 @@
+div.olMap {
+	z-index: 0;
+    padding: 0px!important;
+    margin: 0px!important;
+}
+
+div.olMapViewport {
+    text-align: left;
+}
+
 div.olLayerDiv {
-   -moz-user-select: none 
+   -moz-user-select: none;
 }
 
 .olLayerGoogleCopyright {
@@ -30,15 +40,22 @@
    font-size: xx-small;
 }
 .olControlScaleLineBottom {
-   border: solid 2px black;
-   border-bottom: none;
-   margin-top:-2px;
-   text-align: center;
+   background-color: #000;
+   border: 1px solid #fff;
+   margin: 1px;
+    font-size: 9px;
+    padding-left: 2px;
+    line-height: 12px;
+    color: #fff;
 }
 .olControlScaleLineTop {
-   border: solid 2px black;
-   border-top: none;
-   text-align: center;
+   background-color: #000;
+   border: 1px solid #fff;
+   margin: 1px;
+    font-size: 9px;
+    padding-left: 2px;
+    line-height: 12px;
+    color: #fff;
 }
 
 .olControlPermalink {
@@ -130,29 +147,24 @@
   position: relative;
 }
 
-.olControlNavigationHistoryPreviousItemActive { 
-   background-image: url("img/view_previous_on.png");
+.olControlNavigationHistory {
+   background-image: url("img/navigation_history.png");
    background-repeat: no-repeat;
    width:  24px;
    height: 24px;
+
 }
+.olControlNavigationHistoryPreviousItemActive { 
+  background-position: 0px 0px;
+}
 .olControlNavigationHistoryPreviousItemInactive { 
-   background-image: url("img/view_previous_off.png");
-   background-repeat: no-repeat;
-   width:  24px;
-   height: 24px;
+   background-position: 0px -24px;
 }
 .olControlNavigationHistoryNextItemActive { 
-   background-image: url("img/view_next_on.png");
-   background-repeat: no-repeat;
-   width:  24px;
-   height: 24px;
+   background-position: -24px 0px;
 }
 .olControlNavigationHistoryNextItemInactive { 
-   background-image: url("img/view_next_off.png");
-   background-repeat: no-repeat;
-   width:  24px;
-   height: 24px;
+   background-position: -24px -24px;
 }
 
 .olControlNavToolbar .olControlNavigationItemActive { 
@@ -179,51 +191,47 @@
     width: 200px;
 }
 .olControlEditingToolbar div { 
+  background-image: url("img/editing_tool_bar.png");
+  background-repeat: no-repeat;
   float:right;
   width:  24px;
   height: 24px;
   margin: 5px;
 }
 .olControlEditingToolbar .olControlNavigationItemActive { 
-  background-image: url("img/editing_tool_bar.png");
-  background-repeat: no-repeat;
   background-position: -103px -23px; 
 }
 .olControlEditingToolbar .olControlNavigationItemInactive { 
-  background-image: url("img/editing_tool_bar.png");
-  background-repeat: no-repeat;
   background-position: -103px -0px; 
 }
 .olControlEditingToolbar .olControlDrawFeaturePointItemActive { 
-  background-image: url("img/editing_tool_bar.png");
-  background-repeat: no-repeat;
   background-position: -77px -23px; 
 }
 .olControlEditingToolbar .olControlDrawFeaturePointItemInactive { 
-  background-image: url("img/editing_tool_bar.png");
-  background-repeat: no-repeat;
   background-position: -77px -0px; 
 }
 .olControlEditingToolbar .olControlDrawFeaturePathItemInactive { 
-  background-image: url("img/editing_tool_bar.png");
-  background-repeat: no-repeat;
   background-position: -51px 0px; 
 }
 .olControlEditingToolbar .olControlDrawFeaturePathItemActive { 
-  background-image: url("img/editing_tool_bar.png");
-  background-repeat: no-repeat;
   background-position: -51px -23px; 
 }
 .olControlEditingToolbar .olControlDrawFeaturePolygonItemInactive { 
-  background-image: url("img/editing_tool_bar.png");
-  background-repeat: no-repeat;
   background-position: -26px 0px; 
 }
 .olControlEditingToolbar .olControlDrawFeaturePolygonItemActive { 
-  background-image: url("img/editing_tool_bar.png");
-  background-repeat: no-repeat;
   background-position: -26px -23px ;                                                                   
 }
+.olControlSaveFeaturesItemActive { 
+    background-image: url(img/save_features_on.png);
+    background-repeat: no-repeat;
+    background-position: 0px 1px;
+}
+.olControlSaveFeaturesItemInactive { 
+    background-image: url(img/save_features_off.png);
+    background-repeat: no-repeat;
+    background-position: 0px 1px;
+}
 
 .olHandlerBoxZoomBox {
     border: 2px solid red;
@@ -232,6 +240,14 @@
     opacity: 0.50;
     font-size: 1px;
     filter: alpha(opacity=50);
+}
+.olHandlerBoxSelectFeature {
+    border: 2px solid blue;
+    position: absolute;
+    background-color: white;
+    opacity: 0.50;
+    font-size: 1px;
+    filter: alpha(opacity=50);
 }   
 
 /* 



More information about the fusion-commits mailing list